The Problem
As creators, we are frequently faced with the challenge of creating value by developing apps and products that people want or need to use. The problem though is getting an app to this point typically requires tons of money and time. All the while, there remains uncertainty if users will actually want or use your app. The typical approach to this challenge is more upfront research, market analysis and design meant to validate the work before too many resources are applied to the project. While all the previously mentioned things are necessary, planning can only go so far. Sometimes, you just need to get your app in front of users to truly understand if your idea or solution is valuable. So how can we reconcile the need to have a working app with the costs and risks involved? Answer..... Minimum Viable Product.
What's an MVP?
Minimum Viable Products (MVPs) are intended to showcase the core functionality of an app to users. This is done in order to validate your solution before too much time, money and effort is applied. Because of this, it's important that your MVP be able to stand alone and function without all the bells and whistles. For example: if you want to build a car, an MVP might be building a go cart—this is in contrast to building just the car frame. Remember, you're trying to figure out if people even want to drive (let's assume driving is something new in this make believe scenario). Finishing just one part of your car doesn't help with that question. What you really need is something that a person can ride in, steer, drive and stop. The same could be said for your app! What problem or need are you trying to solve or satisfy? Build only what is necessary to validate your solution—and build it quickly! Then get your users to tell you if your solution needs any changes. Do this right and you will spend the least amount of resources while creating the largest possible value.
The Solution
MeshyDB was created to solve this problem by making it simple and fast to get a working app up and running —we're talking minutes or hours not days or weeks. This article is intended to showcase how you could build a mobile MVP in minutes. The specific example we will be covering is a blogging app for Android phones. If after reading this article you think this solution could work for you, we suggest checking out the complete solution via GitHub. Don't forget though, you need a free MeshyDB account to make this project run.
Creating a RESTful Back-end
In this example, MeshyDB will serve as the RESTful back-end, meaning your application data will be stored and accessed in and through MeshyDB. As a result, you will need to make sure you have a MeshyDB account before you can begin.
Grabbing Your Account Name
Once your account is created and verified, you will want to grab your account name. This unique identifier will help the SDK know where to store and access your data. Not sure what your account name is? Simply go to your Account
page and grab your account name
.
Grabbing Your API Key
Now that you have an account, you will need to locate your public key
. This value is used to identify your app. You can find your default public key
under the Clients
page for your Default
tenant. Copy this value, you're going to need it later. :)
Create a New Project
Now that we have our back-end, let's create our application using Visual Studio 2019 and Xamarin Android SDK. In this example, we will be creating a Xamarin Forms Application If you do not have the option available, you might need to update or install Xamarin (https://visualstudio.microsoft.com/xamarin/).
Getting the Android Emulator Setup
We suggest running the project as-is to get your local Android emulator configured. Setting the Android project as your default startup project and running the app will initiate the device setup. This process can take a few minutes.
Attention! Xamarin emulators can perform very slowly on local workstations, we recommend enabling hardware acceleration on your device to see significant performance improvements.
See this article by Microsoft for how to enable hardware acceleration to improve emulator performance on your local workstation.
Adding MeshyDB SDK
Once you have your project created, you will need to add the NuGet dependency via Package Manager Console. Simply open the console and type Install-Package MeshyDB.SDK
— make sure you run this command for the cross-platform project (not the iOS or Android one). This package will give you a set of libraries used to integrate your app with MeshyDB. While this package is not required, it will definitely make building a C# app much more pleasant.
For more information about the MeshyDB NuGet package, please see NuGet.org (https://www.nuget.org/packages/meshyDb.sdk/).
Establishing a Connection
Because all operations against the MeshyDB API must be performed as an authenticated user, we must first register a user. For the sake of this example, we are going to focus on anonymous users. Think of an anonymous user as a device based user, meaning each device connected to your app will be given a unique identifier. The benefit of this particular user model is its low barrier to entry for users. There is no user registration or account setup, simply create a user behind the scenes using an ID unique to the device and you are off and running.
To accomplish this, we are going to add some code to the App.xaml.cs file generated automatically by Xamarin Forms.
Configuring the Client
MeshyDB is built as a RESTful API, however to make things easier for developers, we've created an SDK that handles all the HTTPS traffic for you. The core of this SDK is the MeshyClient
. Before we can begin, we need to create a static
property that manages our connection. The Client property requires you enter your account name and client id from above.
private static IMeshyClient client;
public static IMeshyClient Client
{
get
{
if (client == null)
{
var accountName = "<!-- your account name -->";
var publicKey = "<!-- your client id -->";
client = MeshyClient.Initialize(accountName, publicKey);
}
return client;
}
}
Registering the User's Device
First, let's check to see if the device is registered and if not, let's register the device as a user via the RegisterDeviceUser()
function. This function uses the DeviceID
property to create an anonymous user. We will initiate this process when the application starts up by invoking the InitializeMeshy()
function in the App()
constructor.
The code below shows how to create and store a unique identifier using Xamarin's Preferences feature. This feature does not persist preferences across app installations, meaning if the user uninstalls and then reinstalls the app, they will get a new id.
public static Guid DeviceId
{
get
{
var id = Preferences.Get("device_id", string.Empty);
if (string.IsNullOrWhiteSpace(id))
{
id = System.Guid.NewGuid().ToString();
Preferences.Set("device_id", id);
}
return new Guid(id);
}
}
private void RegisterDeviceUser()
{
var userExists = Client.CheckUserExist(DeviceId.ToString());
if (!userExists.Exists)
{
Client.RegisterAnonymousUser(DeviceId.ToString());
}
}
public App()
{
InitializeComponent();
InitializeMeshy();
MainPage = new MainPage();
}
private void InitializeMeshy()
{
RegisterDeviceUser();
}
Authenticating the User's Device
Once the user is registered, we then need to authenticate that user / device using the LoginAnonymously(string id)
function. This function performs an anonymous login using the DeviceID
property and then returns an IMeshyConnection
. We will lazy load this connection when the Connection
Property is referenced.
private static IMeshyConnection connection;
public static IMeshyConnection Connection
{
get
{
if (connection == null)
{
connection = Client.LoginAnonymously(DeviceId.ToString());
}
return connection;
}
}
Defining Your Data
The core of MeshyDB is the Mesh. Think of a Mesh as a data structure you define at runtime; with it you can perform read, write, update and delete operations.
For the sake of this example, we need a model that represents a blog post. The template project we implemented already has a model defined called Item.cs. We need to make a few changes to this model before we can start saving data. First, the model needs to extend MesyDB.SDK.Models.MeshData
. Also, we need to add a few more pieces of information relevant to our example. Lastly, we want to store this data in our back-end under a different name, so let's add the [MeshName]
decorator and tell our API to call this data "BlogPost"
.
Remember, this can be whatever you want it to be–there are no requirements outside of implementing MeshyDB.SDK.Models.MeshData
.
[MeshName("BlogPost")]
public class Item : MeshyDB.SDK.Models.MeshData
{
public string Text { get; set; }
public string Description { get; set; }
public string CreatedById { get; set; }
public DateTimeOffset DateCreated { get; set; }
public string CreatedByName { get; set; }
}
At this point, your project is going to be pretty upset with you. By extending MeshyDB.SDK.MOdels.MeshData
, you are making Item.Id
a read only property. The problem is the existing project has some mock data setup where Item.ID
is set. We need to comment out this assignment to make your project happy again.
public MockDataStore()
{
items = new List<Item>();
var mockItems = new List<Item>
{
new Item { Text = "First item",
Description="This is an item description." },
new Item { Text = "Second item",
Description="This is an item description." },
new Item { Text = "Third item",
Description="This is an item description." },
new Item { Text = "Fourth item",
Description="This is an item description." },
new Item { Text = "Fifth item",
Description="This is an item description." },
new Item { Text = "Sixth item",
Description="This is an item description." }
};
foreach (var item in mockItems)
{
items.Add(item);
}
}
Interacting with MeshyDB API (CRUD)
Now that we have our object defined, we need to write the code necessary to interact with the back-end. We can make these necessary changes to the existing ItemsViewModel.cs file.
Adding Records
The constructor already has an event handler that facilitates adding data, we simply need to change how it adds data from writing to a local db to using our MeshyDB API instead. This is done using the CreateAsync<Item>()
function given to us by our SDK.
MessagingCenter.Subscribe<NewItemPage, Item>(this, "AddItem", async (obj, item)=>
{
var newItem = item as Item;
newItem.CreatedById = App.Connection.CurrentUser.Id;
newItem.DateCreated = DateTimeOffset.Now;
var username = App.Connection.CurrentUser.Username;
var name = $"{App.Connection.CurrentUser.FirstName}
{App.Connection.CurrentUser.LastName}".Trim();
username = !string.IsNullOrWhiteSpace(name) ? name : username;
newItem.CreatedByName = username;
try
{
var createResult = await App.Connection.Meshes.CreateAsync<Item>(newItem);
Items.Insert(0, createResult);
}
catch (Exception)
{
throw;
}
});
Deleting Records
Deleting records can be done in a similar way. A new message subscription needs to be added to the constructor that listens for delete actions and calls the DeleteAsync()
function.
MessagingCenter.Subscribe<ItemDetailPage, Item>(this, "DeleteItem", async (obj, item) =>
{
try
{
await App.Connection.Meshes.DeleteAsync<Item>(item.Id);
Items.Remove(item);
}
catch (Exception)
{
throw;
}
});
Editing Records
A third message subscription needs to be created that detects save operations and pushes the new model to the MeshyDB API. As with the create and delete operations, the SDK has an UpdateAsync
function for updating a specific record by id.
MessagingCenter.Subscribe<EditItemPage, Item>(this, "UpdateItem", async (obj, item) =>
{
var username = App.Connection.CurrentUser.Username;
var name = $"{App.Connection.CurrentUser.FirstName}
{App.Connection.CurrentUser.LastName}".Trim();
username = !string.IsNullOrWhiteSpace(name) ? name : username;
item.CreatedByName = username;
try
{
var updatedResult = await App.Connection.Meshes.UpdateAsync<Item>(item.Id, item);
var post = Items.FirstOrDefault(x => x.Id == updatedResult.Id);
post = updatedResult;
}
catch (Exception)
{
throw;
}
});
Loading Data
In the ItemsViewModel.cs, we need to make sure we have the proper commands defined to handle loading data and scrolling for more. The quick start project gives you a pattern for defining LoadItemsCommand
in the constructor. We need to copy that same pattern for the LoadMoreItemsCommand
.
public ObservableCollection<Item>
Items { get; set; }
public Command LoadItemsCommand { get; set; }
public Command LoadMoreItemsCommand { get; set; }
public ItemsViewModel()
{
Title = "Recent Posts";
Items = new ObservableCollection<Item>();
LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
LoadMoreItemsCommand = new Command(async () => await ExecuteLoadItemsCommand(true));
...
}
The existing ExecuteLoadItemsCommand()
function needs to be modified to load from the MeshyDB API rather than the local db. This can be accomplished with the SearchAsync()
function along with some basic filtering and sorting expressions. The example below demonstrates how to filter and sort data by using lambda expressions and our strongly typed model.
In this example, we want users to be able to scroll down to load more data. To support this feature, some additional logic has been added to the ExecuteLoadItemsCommand()
function. Calling this function with the more
parameter as true
will load the next 10 records older than the last one in the current list.
async Task ExecuteLoadItemsCommand(bool more = false)
{
if (IsBusy)
return;
IsBusy = true;
try
{
Expression<Func<Item, bool>> filter = (Item x) => true;
if (more)
{
var lastItem = Items[Items.Count - 1];
filter = (Item x) => x.DateCreated < lastItem.DateCreated;
}
else
{
Items.Clear();
}
var sort = SortDefinition<Item>.SortByDescending(x => x.DateCreated);
var items = await App.Connection.Meshes.SearchAsync(filter, sort, 1, 10);
if (items.TotalRecords > 0)
{
foreach (var item in items.Results)
{
Items.Add(item);
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
The User Interface
Now let's create a place to enter data. Fortunately, the template project does most of this work for us; we simply need to make a few modifications relevant to this example.
Adding a Blog Post
The template creates an xaml file that defines the UI for new item creation NewItemPage.xaml. We just need to make a few changes to the field labels and make the description support multi-line input.
<ContentPage.Content>
<StackLayout Spacing="20" Padding="15">
<Label Text="Title" FontSize="Medium" />
<Entry Text="{Binding Item.Text}" FontSize="Small" />
<Label Text="Post" FontSize="Medium" />
<Editor Text="{Binding Item.Description}" FontSize="Small"
Margin="0" AutoSize="TextChanges" />
</StackLayout>
</ContentPage.Content>
Listing Out Blogs Posts
The template code for listing out records is also pretty close to what we need, however a label for who created the blog needs to be added along with some slight modifications to text formatting.
<DataTemplate>
<ViewCell>
<StackLayout Padding="10">
<Label Text="{Binding Text}"
LineBreakMode="NoWrap"
Style="{DynamicResource ListItemTextStyle}"
FontSize="24" />
<Label Text="{Binding CreatedByName, StringFormat='by {0:N}'}" FontSize="8" />
<Label Text="{Binding Description}"
LineBreakMode="TailTruncation"
MaxLines="3"
Style="{DynamicResource ListItemDetailTextStyle}"
FontSize="13" />
</StackLayout>
</ViewCell>
</DataTemplate>
Scrolling For More
In order to support scrolling for more data, an event handler needs to be wired-up that detects users scrolling to the bottom of the list and loads more data. This can be accomplished via the ItemsListView.ItemAppearing
event. We will detect when the last item is viewed and trigger the LoadMoreItmesCommand
function defined in the ItemsViewModel.cs to load more data.
object lastItem;
public ItemsPage()
{
InitializeComponent();
BindingContext = viewModel = new ItemsViewModel();
ItemsListView.ItemAppearing += (sender, e) =>
{
if (viewModel.IsBusy || viewModel.Items.Count == 0)
return;
var item = e.Item as Item;
if (e.Item != lastItem && item.Id == viewModel.Items[viewModel.Items.Count - 1].Id)
{
lastItem = e.Item;
viewModel.LoadMoreItemsCommand.Execute(null);
}
};
}
Viewing Blog Post Details
The existing UI for viewing details found in ItemDetailPage.xaml already has most of what we need for viewing details. After a couple small tweaks to the UI to show the author and date created, we're all good.
<StackLayout Spacing="20" Padding="15">
<Label Text="{Binding Item.Text}" FontSize="Large" />
<Label Text="{Binding Item.CreatedByName, StringFormat='by {0:N}'}" FontSize="8" />
<Label Text="Post:" FontSize="Medium" />
<Label Text="{Binding Item.Description}" FontSize="Small" />
</StackLayout>
Managing Your Blog Posts
We want to give users the ability to edit or delete their posts, but we only want users to mange their own posts. A new function called InitializeToolbarItems()
will be created that checks the CreatedById
against the App.Connection.CurrentUser.Id
. If the two values match, we know the user viewing the post is the one who created it.
Also, a message subscription needs to be added for when the model you are viewing is updated. To do this, you will need to add a new function called InitializeMessagingSubscription
. This function's job is to listen for updates in the app and make sure the model being viewed by the user stays up-to-date.
ItemDetailViewModel viewModel;
public ItemDetailPage(ItemDetailViewModel viewModel)
{
InitializeComponent();
BindingContext = this.viewModel = viewModel;
InitializeToolbarItems();
InitializeMessagingSubscription();
}
private void InitializeMessagingSubscription()
{
MessagingCenter.Subscribe<EditItemPage, Item>(this, "UpdateItem", (obj, item) =>
{
if (item.Id == this.viewModel.Item.Id)
{
BindingContext = viewModel = new ItemDetailViewModel(item);
}
});
}
private void InitializeToolbarItems()
{
if (viewModel.Item.CreatedById == App.Connection.CurrentUser.Id)
{
ToolbarItems.Add(new ToolbarItem
("Edit", null, new Action(async () => await EditItem())));
ToolbarItems.Add(new ToolbarItem
("Delete", null, new Action(async () => await DeleteItem())));
}
}
Deleting Your Blog Posts
Deleting the post is as simple as sending a message with the model we want to remove. Remember, the ItemsViewModel
has already been configured to listen to this message and perform the delete.
private async Task DeleteItem()
{
var confirm = await DisplayAlert("Delete This Item?",
"Are you sure you want to delete this item?", "YES", "NO");
if (confirm)
{
MessagingCenter.Send(this, "DeleteItem", viewModel.Item);
await Navigation.PopAsync();
}
}
Editing Your Blogs Posts
Unlike deletion, editing requires a bit more. We need to create a new function that opens a new interface for editing passing in the view model.
private async Task EditItem()
{
var editPage = new NavigationPage(new EditItemPage(viewModel));
await Navigation.PushModalAsync(editPage);
}
Now that we have our edit navigation wired up to open a new page, we need to create that new edit page. This is where users will perform the updates.
<ContentPage.ToolbarItems>
<ToolbarItem Text="Cancel" Clicked="Cancel_Clicked" />
<ToolbarItem Text="Update" Clicked="Update_Clicked" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout Spacing="20" Padding="15">
<Label Text="Text" FontSize="Medium" />
<Entry Text="{Binding Item.Text}" FontSize="Small" />
<Label Text="Description" FontSize="Medium" />
<Editor Text="{Binding Item.Description}" FontSize="Small"
Margin="0" AutoSize="TextChanges" />
</StackLayout>
</ContentPage.Content>
The corresponding code behind file needs to be created, however since we already created the message handlers in ItemsViewModel.cs, the only thing we need to do here is emit the message of "UpdateItem
" and pass through the modified object.
[DesignTimeVisible(false)]
public partial class EditItemPage : ContentPage
{
ItemDetailViewModel viewModel;
public EditItemPage()
{
InitializeComponent();
var item = new Item();
viewModel = new ItemDetailViewModel(item);
BindingContext = viewModel;
}
public EditItemPage(ItemDetailViewModel item)
{
InitializeComponent();
BindingContext = viewModel = item;
}
async void Update_Clicked(object sender, EventArgs e)
{
MessagingCenter.Send(this, "UpdateItem", viewModel.Item);
await Navigation.PopModalAsync();
}
async void Cancel_Clicked(object sender, EventArgs e)
{
await Navigation.PopModalAsync();
}
}
Where's All My Data?
Now that we have everything in place, let's run the app and create some blog posts. If we've done everything correctly, you should start seeing data appear in your MeshyDB back-end. To check, simply login to your MeshyDB administrative account and goto Meshes
under the Default
tenant. Find your mesh called BlogPost
and look to see how many documents are in the collection. There should be a document for every post you created.
If you want to dig into the records themselves, simply click on the Mesh to open the details page and click Search
. This will open up a list of all records in the collection. You can view more data about the records by clicking on the record itself.
Troubleshooting
First things first, let's check the logs to see if your calls are succeeding. To do this, let's go to the Dashboard
page under the Default
tenant. Here, you will see your most recent error logs. Clicking the errors will show you the request details and the status code. Below are a list of error status codes and the reasoning behind them:
- 400: Bad request
- Mesh name is invalid and must be alpha characters only.
- Mesh property cannot begin with ‘
$
’ or contain ‘.
’.
- 401: Unauthorized
- User is not authorized to make call.
- Your
client_id
is incorrect.
- 429: Too many requests
- You have either hit your API or Database limit. Please review your account.
If you have no logs, try checking to see that your account name is correct. As long as you have the right account name, you will see error logs in your system.
And That's It!
Believe it or not, you now have an application where users can publish and manage content while also viewing content created by other users. Better yet, your application data is centralized, secure and highly available. Not to mention, you have spent nothing building your app, and have invested only minutes of your time. Congrats!
If you haven't already, we suggest checking out the complete sample application on our GitHub. Enjoy! :)
History
- 27th August, 2019: Initial version