目录
问题
作为创作者,我们经常面临通过开发人们想要或需要使用的应用和产品来创造价值的挑战。但问题是获得应用程序到这一点通常需要大量的金钱和时间。一直以来,如果用户真的想要或使用你的应用程序仍然存在不确定性。解决这一挑战的典型方法是更多的前期研究,市场分析和设计,旨在将太多资源应用于项目之前的验证工作。虽然前面提到的所有内容都是必要的,但规划只能到目前为止。有时,您只需要在用户面前展示您的应用程序,以真正了解您的想法或解决方案是否有价值。那么,我们如何才能将工作应用程序与所涉及的成本和风险相协调呢?答案.....最小可行产品。
什么是MVP?
最小可行产品(MVP)旨在向用户展示应用程序的核心功能。这样做是为了在应用太多时间、金钱和精力之前验证您的解决方案。 因此,重要的是你的MVP能够独立运作而没有所有的花里胡哨。例如:如果你想建造一辆汽车,MVP可能正在建造一辆推车——这与仅建造汽车车架形成鲜明对比。记住,你是想弄清楚人们是否还想开车(让我们假设驾驶是这个让人相信的新事物)。完成汽车的一部分并没有帮助解决这个问题。你真正需要的是一个人可以驾驶和停止的东西。您的应用程序也可以这样说!您试图解决或满足的问题或需求是什么?只构建验证解决方案所需的内容,并快速构建它!然后让您的用户告诉您解决方案是否需要进行任何更改。这样做是正确的,您将花费最少的资源,同时创造尽可能大的价值。
解决方案
创建MeshyDB是为了解决这个问题,方法是简单快速地启动和运行正常运行的应用程序——我们说几分钟或几小时而不是几天或几周。本文旨在展示如何在几分钟内构建移动MVP。我们将介绍的具体示例是适用于Android手机的博客应用。如果您在阅读本文后认为此解决方案可能对您有用,我们建议您通过GitHub查看完整的解决方案。不要忘了,你需要一个免费的MeshyDB帐户来运行这个项目。
创建RESTful后端
在此示例中,MeshyDB将充当RESTful后端,这意味着您的应用程序数据将通过MeshyDB存储和访问。因此,在开始之前,您需要确保拥有一个MeshyDB帐户。
抓取您的帐户名称
创建并验证帐户后,您需要获取帐户名称。此唯一标识符将帮助SDK知道存储和访问数据的位置。不确定您的帐户名称是什么?只需转到您的Account页面并抓取您的帐户name即可。
抓取您的API密钥
现在你有了一个帐户,你需要找到你的public key。此值用于标识您的应用程序。您可以在Default租户的Clients页面下找到您的默认public key值。复制此值,稍后您将需要它。:)
创建一个新项目
现在我们有了后端,让我们使用Visual Studio 2019和Xamarin Android SDK创建我们的应用程序。在此示例中,我们将创建一个Xamarin Forms应用程序,如果您没有可用选项,则可能需要更新或安装Xamarin(https://visualstudio.microsoft.com/xamarin/)。
获取Android模拟器设置
我们建议按原样运行项目以配置您的本地Android模拟器。将Android项目设置为默认启动项目并运行应用程序将启动设备设置。此过程可能需要几分钟时间。
注意!Xamarin模拟器在本地工作站上的执行速度非常慢,我们建议您在设备上启用硬件加速,以显着提高性能。有关如何启用硬件加速以提高本地工作站上的模拟器性能的信息,请参阅Microsoft的这篇文章。
添加MeshyDB SDK
创建项目后,需要通过Package Manager Console添加NuGet依赖项。只需打开控制台并键入 Install-Package MeshyDB.SDK——确保为跨平台项目(不是iOS或Android)运行此命令。这个包将为您提供一组用于将您的应用程序与MeshyDB集成的库。虽然不需要这个软件包,但它肯定会使构建C#应用程序更加愉快。
有关MeshyDB NuGet包的更多信息,请参阅NuGet.org(https://www.nuget.org/packages/meshyDb.sdk/)。
建立连接
因为针对MeshyDB API的所有操作必须作为经过身份验证的用户执行,所以我们必须首先注册用户。为了这个例子,我们将专注于匿名用户。将匿名用户视为基于设备的用户,这意味着连接到您应用的每台设备都将获得唯一标识符。这种特定用户模型的好处是其用户进入门槛低。没有用户注册或帐户设置,只需使用设备特有的ID在幕后创建用户即可关闭并运行。
为此,我们将向Xamarin Forms自动生成的App.xaml.cs文件中添加一些代码。
配置客户端
MeshyDB是作为RESTful API构建的,但是为了使开发人员更容易,我们创建了一个SDK来处理所有的HTTPS流量。这个SDK的核心是MeshyClient。在我们开始之前,我们需要创建一个管理连接的static属性。客户属性要求您从上面输入您的帐户名和客户ID。
App.xaml.cs (在GitHub中查看)
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;
}
}
注册用户设备
首先,让我们检查设备是否已注册,如果没有,请通过RegisterDeviceUser()函数将设备注册为用户。此函数使用DeviceID属性来创建匿名用户。我们将在应用程序启动时通过调用App()构造函数中的InitializeMeshy()函数来启动此过程。
下面的代码显示了如何使用Xamarin的Preferences功能创建和存储唯一标识符。此功能不会在应用安装中保留首选项,这意味着如果用户卸载然后重新安装应用,则会获得新ID。
App.xaml.cs (在GitHub中查看)
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();
}
验证用户的设备
一旦用户注册,我们就需要使用LoginAnonymously(string id)函数对该用户/设备进行身份验证。函数使用DeviceID属性执行匿名登录,然后返回IMeshyConnection。当引用Connection属性时,我们将延迟加载此连接。
App.xaml.cs (在GitHub中查看)
private static IMeshyConnection connection;
public static IMeshyConnection Connection
{
get
{
if (connection == null)
{
connection = Client.LoginAnonymously(DeviceId.ToString());
}
return connection;
}
}
定义您的数据
MeshyDB的核心是Mesh。将Mesh视为您在运行时定义的数据结构; 使用它,您可以执行读取、写入、更新和删除操作。
为了这个例子,我们需要一个代表博客文章的模型。我们实现的模板项目已经定义了一个名为Item.cs的模型。在开始保存数据之前,我们需要对此模型进行一些更改。首先,模型需要扩展MesyDB.SDK.Models.MeshData。此外,我们还需要添加一些与我们的示例相关的信息。最后,我们希望以不同的名称将这些数据存储在我们的后端,所以让我们添加[MeshName]装饰器并告诉我们的API来调用这些数据"BlogPost"。
请记住,这可以是你想要的任何东西——除了实现MeshyDB.SDK.Models.MeshData之外没有任何要求。
Item.cs (在GitHub中查看)
[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; }
}
此时,您将对您的项目感到非常沮丧。通过扩展MeshyDB.SDK.MOdels.MeshData,您将创建Item.Id为一个只读属性。问题是现有的项目在设置Item.ID的地方设置了一些模拟数据。我们需要注释掉这项任务,让您的项目再次愉快。
MockDataStore.cs (在GitHub中查看)
public MockDataStore()
{
items = new List<Item>();
var mockItems = new List<Item>
{
new Item { /*Id = Guid.NewGuid().ToString(),*/ Text = "First item",
Description="This is an item description." },
new Item { /*Id = Guid.NewGuid().ToString(),*/ Text = "Second item",
Description="This is an item description." },
new Item { /*Id = Guid.NewGuid().ToString(),*/ Text = "Third item",
Description="This is an item description." },
new Item { /*Id = Guid.NewGuid().ToString(),*/ Text = "Fourth item",
Description="This is an item description." },
new Item { /*Id = Guid.NewGuid().ToString(),*/ Text = "Fifth item",
Description="This is an item description." },
new Item { /*Id = Guid.NewGuid().ToString(),*/ Text = "Sixth item",
Description="This is an item description." }
};
foreach (var item in mockItems)
{
items.Add(item);
}
}
与MeshyDB API(CRUD)交互
现在我们已经定义了对象,我们需要编写与后端交互所需的代码。我们可以对现有的ItemsViewModel.cs文件进行必要的更改。
添加记录
构造函数已经有一个有助于添加数据的事件处理程序,我们只需要更改从写入本地数据库到使用我们的MeshyDB API添加数据的方式。这是使用我们的SDK给我们的CreateAsync<Item>()函数完成的。
ItemViewModel.cs (在GitHub中查看)
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;
}
});
删除记录
删除记录可以以类似的方式完成。需要将新消息订阅添加到侦听删除操作并调用DeleteAsync()函数的构造函数中。
ItemViewModel.cs (在GitHub中查看)
MessagingCenter.Subscribe<ItemDetailPage, Item>(this, "DeleteItem", async (obj, item) =>
{
try
{
await App.Connection.Meshes.DeleteAsync<Item>(item.Id);
Items.Remove(item);
}
catch (Exception)
{
throw;
}
});
编辑记录
需要创建第三个消息订阅,以检测保存操作并将新模型推送到MeshyDB API。与创建和删除操作一样,SDK具有通过id更新特定记录的UpdateAsync函数。
ItemViewModel.cs (在GitHub中查看)
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;
}
});
加载数据
在ItemsViewModel.cs中,我们需要确保定义了适当的命令来处理加载数据和滚动以获得更多。快速启动项目为您提供了在构造函数中定义的LoadItemsCommand模式。我们需要复制相同的LoadMoreItemsCommand模式。
ItemViewModel.cs (在GitHub中查看)
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));
...
}
需要修改现有的ExecuteLoadItemsCommand()函数以从MeshyDB API而不是本地db加载。这可以通过SearchAsync() 函数以及一些基本的过滤和排序表达式来完成。下面的示例演示了如何使用lambda表达式和强类型模型对数据进行过滤和排序。
在此示例中,我们希望用户能够向下滚动以加载更多数据。为了支持此功能,ExecuteLoadItemsCommand()函数添加了一些额外的逻辑。使用more参数作为true调用此函数将加载比当前列表中最后一条记录旧的下10条记录。
ItemViewModel.cs (在GitHub中查看)
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;
}
}
用户界面
现在让我们创建一个输入数据的地方。幸运的是,模板项目为我们完成了大部分工作; 我们只需要做一些与此示例相关的修改。
添加博客帖子
该模板创建一个xaml文件,该文件定义新项目创建NewItemPage.xaml的UI。我们只需要对字段标签进行一些更改,并使描述支持多行输入。
NewItemPage.xaml (在GitHub中查看)
<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>
列出博客帖子
用于列出记录的模板代码也非常接近我们需要的内容,但是需要添加创建博客的标签以及对文本格式的一些细微修改。
ItemsPage.xaml (在GitHub中查看)
<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>
滚动以获得更多
为了支持滚动以获得更多数据,需要连接一个事件处理程序,以检测用户滚动到列表底部并加载更多数据。这可以通过ItemsListView.ItemAppearing事件来完成。我们将检测何时查看最后一项,并触发ItemsViewModel.cs中定义的LoadMoreItmesCommand函数以加载更多数据。
ItemsPage.xaml.cs (在GitHub中查看)
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);
}
};
}
查看博客帖子详情
用于查看ItemDetailPage.xaml中的详细信息的现有UI 已经具有查看详细信息所需的大部分内容。在对UI进行几次小调整以显示作者和创建日期后,我们都很好。
ItemDetailPage.xaml (在GitHub中查看)
<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>
管理您的博客帖子
我们希望用户能够编辑或删除他们的帖子,但我们只希望用户管理他们自己的帖子。将创建一个名为InitializeToolbarItems()的新函数,该函数更具App.Connection.CurrentUser.Id检查CreatedById。如果两个值匹配,我们知道查看帖子的用户是创建它的人。
此外,还需要在更新您正在查看的模型时添加消息订阅。为此,您需要添加一个名为InitializeMessagingSubscription的新函数。此函数的作用是监听应用程序中的更新,并确保用户查看的模型保持最新状态。
ItemDetailPage.xaml.cs (在GitHub中查看)
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())));
}
}
删除您的博客帖子
删除帖子就像使用我们要删除的模型发送消息一样简单。请记住,ItemsViewModel已经配置为侦听此消息并执行删除。
ItemDetailPage.xaml.cs (在GitHub中查看)
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();
}
}
编辑您的博客帖子
与删除不同,编辑需要更多。我们需要创建一个新函数,打开一个新的界面,用于编辑视图模型中的传递。
ItemDetailPage.xaml.cs (在GitHub中查看)
private async Task EditItem()
{
var editPage = new NavigationPage(new EditItemPage(viewModel));
await Navigation.PushModalAsync(editPage);
}
现在我们已经连接了编辑导航以打开新页面,我们需要创建新的编辑页面。这是用户执行更新的地方。
EditItemPage.xaml (在GitHub中查看)
<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>
需要创建相应的代码隐藏文件,但是因为我们已经在ItemsViewModel.cs中创建了消息处理程序,所以我们唯一要做的就是发出消息“UpdateItem”并传递修改后的对象。
EditItemPage.xaml.cs (在GitHub中查看)
[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();
}
}
我的所有数据都在哪里?
现在我们已经掌握了一切,让我们运行应用程序并创建一些博客文章。如果我们已正确完成所有操作,您应该开始看到MeshyDB后端中出现的数据。要进行检查,只需登录到您的MeshyDB管理帐户,并转到Meshes下Default租户。找到名叫BlogPost的mesh,并查看集合中有多少文档。您创建的每个帖子都应该有一个文档。
如果您想深入了解记录本身,只需单击Mesh即可打开详细信息页面并单击Search。这将打开集合中所有记录的列表。您可以通过单击记录本身来查看有关记录的更多数据。
故障排除
首先,让我们检查日志以查看您的调用是否成功。为此,我们转到Default租户下的Dashboard页面。在这里,您将看到最新的错误日志。单击错误将显示请求详细信息和状态代码。以下是错误状态代码列表及其背后的原因:
- 400:错误请求
- Mesh名称无效,且必须仅为字母字符。
- Mesh属性不能以'$'开头或包含.' 。
- 401:未经授权
- 用户无权调用。
- 你的client_id不对。
- 429:请求过多
- 您已达到API或数据库限制。请检查您的帐户。
如果您没有日志,请尝试检查您的帐户名称是否正确。只要您拥有正确的帐户名称,您就会在系统中看到错误日志。
就是这样!
信不信由你,你现在有一个应用程序,用户可以发布和管理内容,同时还可以查看其他用户创建的内容。更好的是,您的应用程序数据是集中、安全且高度可用的。更不用说,你没有花费任何东西来构建你的应用程序,并且只投入了几分钟的时间。恭喜!
如果您还没有,我们建议您在我们的GitHub上查看完整的示例应用程序。请享用!:)