使用 BaaS 更快地构建 Xamarin 应用





5.00/5 (1投票)
了解如何快速构建使用 Xamarin 和云托管移动后端(只需几分钟)的移动应用。
问题
作为创作者,我们经常面临通过开发人们想要或需要使用的应用程序和产品来创造价值的挑战。然而,问题在于,让一个应用程序达到这一点通常需要大量的金钱和时间。同时,用户是否实际想要或使用你的应用程序仍然是不确定的。应对这一挑战的典型方法是进行更多的初步研究、市场分析和设计,以在投入过多资源之前验证工作。虽然以上所有内容都是必要的,但规划只能到此为止。有时,你只需要将你的应用程序展示给用户,才能真正了解你的想法或解决方案是否有价值。那么,我们如何才能解决拥有一个可行的应用程序的需求与所涉及的成本和风险之间的矛盾呢?答案是......最小可行产品。
什么是 MVP?
最小可行产品 (MVP) 旨在向用户展示应用程序的核心功能。这样做是为了在投入过多时间、金钱和精力之前验证你的解决方案。因此,重要的是你的 MVP 能够独立运行,而无需所有花里胡哨的东西。例如:如果你想制造一辆汽车,MVP 可能就是制造一辆卡丁车——这与只制造汽车框架形成对比。记住,你试图弄清楚人们是否想开车(假设在这个虚构的场景中,驾驶是全新的东西)。仅仅完成你汽车的一个部分并不能帮助回答这个问题。你真正需要的是人们可以乘坐、驾驶、行驶和停止的东西。你的应用程序也可能如此!你想解决或满足什么问题或需求?只构建验证你的解决方案所需的东西——并快速构建它!然后让你的用户告诉你你的解决方案是否需要任何更改。这样做,你将花费最少的资源,同时创造最大的价值。
解决方案
MeshyDB 的创建是为了解决这个问题,使应用程序的开发变得简单快捷——我们说的是几分钟或几小时,而不是几天或几周。本文旨在展示如何用几分钟的时间构建一个移动 MVP。我们将要介绍的具体示例是 Android 手机的博客应用程序。如果你在阅读本文后认为此解决方案适合你,我们建议通过 GitHub 查看完整解决方案。但别忘了,你需要一个免费的 MeshyDB 账户才能运行此项目。
创建 RESTful 后端
在此示例中,MeshyDB 将作为 RESTful 后端,这意味着你的应用程序数据将在 MeshyDB 中存储和访问。因此,你需要确保在开始之前有一个 MeshyDB 账户。
获取你的账户名称
创建并验证你的账户后,你需要获取你的账户名称。这个唯一的标识符将帮助 SDK 知道在哪里存储和访问你的数据。不确定你的账户名称是什么?只需转到你的 账户
页面并获取你的账户 名称
。
获取你的 API 密钥
现在你已经有了账户,你需要找到你的 公钥
。此值用于标识你的应用程序。你可以在你的 默认
租户的 客户端
页面下找到你的默认 公钥
。复制此值,稍后你会用到它。 :)
创建新项目
现在我们有了后端,让我们使用 Visual Studio 2019 和 Xamarin Android SDK 来创建我们的应用程序。在本例中,我们将创建一个Xamarin Forms 应用程序。如果你没有该选项,你可能需要更新或安装 Xamarin (https://visualstudio.microsoft.com/xamarin/)。
设置 Android 模拟器
我们建议按原样运行项目以配置你的本地 Android 模拟器。将 Android 项目设置为默认启动项目并运行应用程序将启动设备设置。此过程可能需要几分钟。
添加 MeshyDB SDK
创建项目后,你需要通过程序包管理器控制台添加 NuGet 依赖项。只需打开控制台并键入 Install-Package MeshyDB.SDK
——请确保为跨平台项目(而不是 iOS 或 Android 项目)运行此命令。此程序包将为你提供一组用于将你的应用程序与 MeshyDB 集成的库。虽然此程序包不是必需的,但它肯定会让构建 C# 应用程序更加愉快。
有关 MeshyDB NuGet 程序包的更多信息,请参阅 NuGet.org (https://nuget.net.cn/packages/meshyDb.sdk/)。
建立连接
由于所有针对 MeshyDB API 的操作都必须作为已认证的用户执行,因此我们必须首先注册一个用户。在本例中,我们将重点关注匿名用户。将匿名用户视为基于设备的用户,这意味着连接到你的应用程序的每个设备都将获得一个唯一的标识符。这种特定用户模型的优点是用户入门门槛低。没有用户注册或账户设置,只需在后台使用设备唯一的 ID 创建一个用户,即可开始使用。
要实现这一点,我们将向 Xamarin Forms 自动生成的 App.xaml.cs 文件添加一些代码。
配置客户端
MeshyDB 是一个 RESTful API,但为了方便开发者,我们创建了一个处理所有 HTTPS 流量的 SDK。该 SDK 的核心是 MeshyClient
。在我们开始之前,我们需要创建一个管理我们连接的静态
属性。Client 属性要求你输入上面提供的账户名称和客户端 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 提供了一个 UpdateAsync
函数来按 ID 更新特定记录。
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 而不是本地数据库加载。这可以通过 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 文件,用于定义新项目创建的 UI NewItemPage.xaml。我们只需要对字段标签进行一些更改,并使描述支持多行输入。
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()
的新函数,该函数将 CreatedById
与 App.Connection.CurrentUser.Id
进行比较。如果两个值匹配,我们就知道正在查看帖子的用户就是创建者。
此外,还需要添加一个消息订阅,用于在查看的模型更新时接收通知。为此,你需要添加一个名为 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
。找到你称为“BlogPost”
的 Mesh,看看集合中有多少文档。你应该为每个你创建的帖子都有一个文档。
如果你想深入研究记录本身,只需单击 Mesh 打开详细信息页面,然后单击 Search
。这将打开一个包含集合中所有记录的列表。你可以通过单击记录本身来查看有关记录的更多数据。
故障排除
首先,让我们检查日志,看看你的调用是否成功。为此,让我们转到 默认
租户下的 Dashboard
页面。在这里,你将看到最新的错误日志。单击错误将显示请求详细信息和状态代码。以下是错误状态代码及其原因列表
- 400:错误请求
- Mesh 名称无效,只能包含字母字符。
- Mesh 属性不能以“
$
”开头或包含“.
”。
- 401:未授权
- 用户未获准执行调用。
- 你的
client_id
不正确。
- 429:请求过多
- 你已达到 API 或数据库限制。请查看你的账户。
如果你没有日志,请尝试检查你的账户名称是否正确。只要你有正确的账户名称,你就会在系统中看到错误日志。
就是这样!
信不信由你,你现在已经拥有了一个应用程序,用户可以在其中发布和管理内容,同时还能查看其他用户创建的内容。更妙的是,你的应用程序数据是集中式的、安全的并且高度可用的。更不用说,你花费了零成本来构建你的应用程序,并且只投入了几分钟的时间。恭喜!
如果你还没有这样做,我们建议在我们的 GitHub 上查看完整的示例应用程序。祝你使用愉快! :)
历史
- 2019年8月27日:初始版本