Если вас раздражает аспект обновления данных, хранящихся в моделях, и вы думаете о том, как было бы здорово, если бы модель могла быть более самостоятельной и уведомлять об изменениях, добро пожаловать под кат.
Примечание. Далее повествование будет вестись от имени автора.
Будучи разработчиком .NET, я неравнодушен к MVVM и привязке данных. Благодаря им я могу отвязать представления от логики приложения, и, в большинстве случаев, писать приложение без необходимости беспокоиться об обновлении пользовательского интерфейса. Однако, остается один раздражающий аспект — обновление данных, хранящихся в моделях. Чаще всего я получаю сетевой вызов, который принимает определенные данные, сохраняет их на диске, а затем обновляет модели представлений посредством обертывания соответствующей модели, будь то путем сравнения изменений или же обновления всего интерфейса в целом. Но ведь было бы здорово, если модель могла самостоятельно об этом позаботиться и уведомить нас об изменениях, не правда ли? Оказывается, при использовании Realm для долговременного хранения данных это становится возможным.
Это может стать неожиданностью для некоторых, но все объекты, хранящиеся в Realm, изначально доступны для наблюдения. И в том числе даже функциональные зависимости и результаты запроса! И благодаря этому их необычайно легко передавать непосредственно к привязке данных. В этом посте я сосредоточусь на Xamarin.Forms, поскольку они поставляются с дружелюбным по отношению к привязке данных механизмом визуализации, но вы можете запросто использовать проект с нативным UI с такими фреймворкоми, как MvvmCross и MVVM Light. Итак, давайте рассмотрим на примере, как, используя Realm, можно создать с его помощью очень простое приложение контактов.
Поскольку мы не хотим ничего здесь усложнять, давайте определим только один класс модели — User:
Здесь мы указываем сохраняемый объект, который благодаря Realm будет постоянно обновляться. Так что, как только у вас появляется экземпляр класса
В первую очередь от приложения контактов ожидают того, что у него будет функциональная возможность для отображения, собственно говоря, списка контактов. В мире MVVM под этим, как правило, подразумевается предоставление
А вот и наша страница (мы должны установить в коде
И вот результат! Единовременная инициализация — и ручное обновление
И соответствующая страница:
С того момента как привязки
Давайте немного расширим функциональность нашего пока что «сырого» приложения, то есть, добавим возможность отметить контакт в качестве избранного. Сначала добавляем новое свойство нашему User:
После этого мы обновляем
Благодаря этому избранные контакты поднимутся вверх, но притом обе группы будут все так же распределяться в алфавитном порядке. И наконец, давайте добавим к нашему элементу кнопку ? (если вас интересует чередование кнопок ? и ? в зависимости от того, добавлен ли контакт в избранное или нет, ознакомьтесь с готовым примером):
Вот и все. Ничего сверхъестественного, конечно, но таким образом мы смогли наглядно продемонстрировать, до какой степени проще добавлять новые функции, когда модели доступны для наблюдения и постоянно обновляются.
Этот вопрос постоянно всплывает, когда речь заходит о наблюдаемых и активных объектах — «Как они влияют на производительность?» Разумеется, ничего не достается бесплатно. И возможность наблюдения за изменениями не является исключением. Так что за использование этой функции приходится расплачиваться небольшой надбавкой в ресурсопотреблении. Хорошая новость заключается в том, что это наблюдение за изменением случается лишь тогда, когда вы подписываетесь на
Realm позволяет с легкостью разрабатывать пользовательский интерфейс приложения на основе данных, которые имеются на диске. Благодаря этому можно разрабатывать по-настоящему емкие в автономном плане приложения. А также, что более важно, создавать пользовательский интерфейс, который будет независимым от того, где возникают изменения модели. Они могут быть результатом действия пользователя, веб-запроса или даже синхронизации с сервером через Realm Mobile Platform (который очень скоро присоединится к сервисам Xamarin). И неважно каким именно результатом они будут — пользователь увидит каждое обновление, как только оно появится.
Если у Вас есть в запасе несколько минут, загляните и заберите базу данных (она создана на базе открытого исходного кода и доступна бесплатно). Или если вы похожи на меня и любите поиграть с готовым проектом, прежде чем читать сообщения в блоге, возьмите код на GitHub.
Александр Алексеев — Xamarin-разработчик, фрилансер. Работает с .NET-платформой с 2012 года. Участвовал в разработке системы автоматизации закупок в компании Digamma. C 2015 года ушел во фриланс и перешел на мобильную разработку с использованием Xamarin. В текущее время работает в компании StecPoint над iOS приложением.
Ведет ресурс XamDev.ru и сообщества «Xamarin Developers» в социальных сетях: VK, Facebook, Telegram.
Другие статьи из нашего блога о Xamarin читайте по ссылке #xamarincolumn.
Примечание. Далее повествование будет вестись от имени автора.
Будучи разработчиком .NET, я неравнодушен к MVVM и привязке данных. Благодаря им я могу отвязать представления от логики приложения, и, в большинстве случаев, писать приложение без необходимости беспокоиться об обновлении пользовательского интерфейса. Однако, остается один раздражающий аспект — обновление данных, хранящихся в моделях. Чаще всего я получаю сетевой вызов, который принимает определенные данные, сохраняет их на диске, а затем обновляет модели представлений посредством обертывания соответствующей модели, будь то путем сравнения изменений или же обновления всего интерфейса в целом. Но ведь было бы здорово, если модель могла самостоятельно об этом позаботиться и уведомить нас об изменениях, не правда ли? Оказывается, при использовании Realm для долговременного хранения данных это становится возможным.
Это может стать неожиданностью для некоторых, но все объекты, хранящиеся в Realm, изначально доступны для наблюдения. И в том числе даже функциональные зависимости и результаты запроса! И благодаря этому их необычайно легко передавать непосредственно к привязке данных. В этом посте я сосредоточусь на Xamarin.Forms, поскольку они поставляются с дружелюбным по отношению к привязке данных механизмом визуализации, но вы можете запросто использовать проект с нативным UI с такими фреймворкоми, как MvvmCross и MVVM Light. Итак, давайте рассмотрим на примере, как, используя Realm, можно создать с его помощью очень простое приложение контактов.
Модели
Поскольку мы не хотим ничего здесь усложнять, давайте определим только один класс модели — User:
public class User : RealmObject
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Name { get; set; }
/* Другие подходящие данные – телефон, адрес и т. Д. */
}
Здесь мы указываем сохраняемый объект, который благодаря Realm будет постоянно обновляться. Так что, как только у вас появляется экземпляр класса
User
нет никакой необходимости «обновлять» его, поскольку всякий раз, когда вы получаете доступ к свойству, текущая информация сохраняется. Кроме того, RealmObject
реализует INotifyPropertyChanged
, что позволяет подписаться и получать уведомления о любых возможных изменениях. И хотя это всего лишь несколько строк кода, их влияние весьма значительное. И что еще лучше, здесь фактически отсутствуют какие-либо шаблоны — нет ручного вызова события, отображения SQL и, безусловно, логики обновления.Список контактов
В первую очередь от приложения контактов ожидают того, что у него будет функциональная возможность для отображения, собственно говоря, списка контактов. В мире MVVM под этим, как правило, подразумевается предоставление
ObservableCollection<User>
во ViewModel
, привязка его, а затем обновление при изменении моделей (например, после добавления нового контакта). Звучит так, будто бы это очень сложно, но посмотрите, как мы справимся с этой задачей при помощи Realm:public class ContactsViewModel
{
private readonly Realm _realm;
public IEnumerable<User> Users { get; }
public ContactsViewModel()
{
_realm = Realm.GetInstance();
Users = _realm.All<User>().OrderBy(u => u.Name);
}
}
А вот и наша страница (мы должны установить в коде
ContactsViewModel
в качестве BindingContext
, но там ничего интересного, поэтому просто предположим, что мы это сделали):<ContentPage x:Class="Contacts.ContactsPage">
<ContentPage.Content>
<ListView ItemsSource="{Binding Users}" x:Name="ContactsListView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Name}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
И вот результат! Единовременная инициализация — и ручное обновление
Users
более не требуется. Тип времени выполнения коллекции, возвращенный Realm.All, реализует INotifyCollectionChanged, за которым следит механизм привязки данных Xamarin Forms, так что интерфейс получает уведомления о любых изменениях, происходящих в коллекции. При работе с нативным проектом UI можно либо преобразовать этот тип самостоятельно, или же использовать метод расширения AsRealmCollection. Теперь в подтверждение моих слов давайте рассмотрим, каким образом мы сможем выполнять определенные изменения. Редактирование единственного контакта
public class EditContactViewModel
{
public User User { get; }
public EditContactViewModel(User user)
{
User = user;
}
}
И соответствующая страница:
<ContentPage x:Class="Contacts.EditContactPage"
Title="{Binding User.Name}">
<ContentPage.Content>
<Entry Text="{Binding User.Name}" Placeholder="Contact name" />
</ContentPage.Content>
</ContentPage>
С того момента как привязки
Entry
стали по умолчанию двунаправленными, когда бы вы ни меняли имя пользователя, изменение сохраняется на диске и заголовок страницы обновляется. И поскольку наш ListView на главном экране связан с «активным» запросом, он будет обновлять соответствующие новые данные уже при нажатии на кнопку «назад». Подключать навигацию к моделям представления не так уж и весело, поэтому я не стану здесь на этом останавливаться. Но с одним из способов это сделать можно ознакомиться в готовом примере.Добавление контакта в избранное
Давайте немного расширим функциональность нашего пока что «сырого» приложения, то есть, добавим возможность отметить контакт в качестве избранного. Сначала добавляем новое свойство нашему User:
public class User : RealmObject
{
/* previous properties */
public bool IsFavorite { get; set; }
}
После этого мы обновляем
ContactsViewModel
, с тем чтобы избранные контакты показывались сверху, а также добавляем команду «Переключить избранное»: public class ContactsViewModel
{
/* Other properties */
public Command<User> ToggleIsFavoriteCommand { get; }
public ContactsViewModel()
{
_realm = Realm.GetInstance();
Users = _realm.All<User>().OrderByDescending(u => u.IsFavorite)
.ThenBy(u => u.Name);
ToggleIsFavoriteCommand = new Command<User>(user =>
{
_realm.Write(() => user.IsFavorite = !user.IsFavorite);
});
}
}
Благодаря этому избранные контакты поднимутся вверх, но притом обе группы будут все так же распределяться в алфавитном порядке. И наконец, давайте добавим к нашему элементу кнопку ? (если вас интересует чередование кнопок ? и ? в зависимости от того, добавлен ли контакт в избранное или нет, ознакомьтесь с готовым примером):
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
</Grid>
<Label Text="{Binding Name}"/>
<Button Text="?" Grid.Column="1"
Command="{Binding Path=BindingContext.ToggleIsFavoriteCommand, Source={x:Reference Name=ContactsListView}}"
CommandParameter="{Binding .}"/>
</ViewCell>
Вот и все. Ничего сверхъестественного, конечно, но таким образом мы смогли наглядно продемонстрировать, до какой степени проще добавлять новые функции, когда модели доступны для наблюдения и постоянно обновляются.
Возможные последствия для производительности
Этот вопрос постоянно всплывает, когда речь заходит о наблюдаемых и активных объектах — «Как они влияют на производительность?» Разумеется, ничего не достается бесплатно. И возможность наблюдения за изменениями не является исключением. Так что за использование этой функции приходится расплачиваться небольшой надбавкой в ресурсопотреблении. Хорошая новость заключается в том, что это наблюдение за изменением случается лишь тогда, когда вы подписываетесь на
PropertyChanged
или CollectionChanged
и останавливается, как только подписка прекращается. Это означает, что если у вас обрабатывается миллион объектов и вы не желаете получать о них уведомления, то возможности наблюдения RealmObjects
не будут никоим образом оказывать на вас влияние. И если вы заботитесь об изменениях и используйте RealmObjects
для привязки данных, то замедление, вызванное уведомлениями, будет ничтожно малым по сравнению с логикой вычислений привязки данных и макета, которые выполняются Xamarin Forms при каждом обновлении.Заключение
Realm позволяет с легкостью разрабатывать пользовательский интерфейс приложения на основе данных, которые имеются на диске. Благодаря этому можно разрабатывать по-настоящему емкие в автономном плане приложения. А также, что более важно, создавать пользовательский интерфейс, который будет независимым от того, где возникают изменения модели. Они могут быть результатом действия пользователя, веб-запроса или даже синхронизации с сервером через Realm Mobile Platform (который очень скоро присоединится к сервисам Xamarin). И неважно каким именно результатом они будут — пользователь увидит каждое обновление, как только оно появится.
Если у Вас есть в запасе несколько минут, загляните и заберите базу данных (она создана на базе открытого исходного кода и доступна бесплатно). Или если вы похожи на меня и любите поиграть с готовым проектом, прежде чем читать сообщения в блоге, возьмите код на GitHub.
Благодарим за перевод
Александр Алексеев — Xamarin-разработчик, фрилансер. Работает с .NET-платформой с 2012 года. Участвовал в разработке системы автоматизации закупок в компании Digamma. C 2015 года ушел во фриланс и перешел на мобильную разработку с использованием Xamarin. В текущее время работает в компании StecPoint над iOS приложением.
Ведет ресурс XamDev.ru и сообщества «Xamarin Developers» в социальных сетях: VK, Facebook, Telegram.
Другие статьи из нашего блога о Xamarin читайте по ссылке #xamarincolumn.
Поделиться с друзьями
Atreides07
Спасибо за статью. Но есть несколько замечаний и вопросы на который я не нашел для себя ответа.
Преимущество Realm не в том что он "вживую" обновляет данные и сам дергает NotifyPropertyChanged. Это все прекрасно умеет и Fody.PropertyChanged. Но в случае Fody вам не придется ваши модели наследовать от конкретного объекта (RealmObject). Собственно если посмотрите исходники Realm для C# то можете что они всю эту "магию" реализуют через Fody
Как и сказано выше, Realm дает преимущество когда есть и сервер на Realm Object Server. Таким образом Realm продвигает идею что вам не надо самим писать логику синхронизации данных с сервером. Просто сохраняйте данные в модели и данные синхронизируются сами.
Я так и не нашел ответа на вопрос и во всех статьях про Realm "забывают" уточнить про этот самый важный вопрос. Приложения почти никогда не пишутся на века. Схема данных меняется. Там где у нас был Category Category {get; set; } завтра может быть IList Tags {get; set;}. Даже проще. Name может быть переименован во FirstName и т.д. и т.п. Как в таком случае мигрировать категории в теги? В случае SQL мы можем на новую схему написать скрипт миграции. А в случае Realm? Я спрашивал тех кто в бою используют Realm. Они по сути забивают на возможность потери данных или стараются ничего не менять без крайней необходимости ради удобства синхронизации данных клиента и сервера.
Следующий вопрос как следствие из предыдущих двух пунктов: Если схема данных меняется и даже есть какой то способ смигрировать данные. Как работает синхронизация данных с сервером если часть устройств обновилась и работает с новой схемой а часть устройство еще не обновилась и работают со старой схемой? В случае ручной работы с API мы можем легко поддержать работоспособность старого API до тех пор пока большинство устройств не обновятся и мигрировать вживую старые модели в новые. А как это все делается в Realm Object Server ?
Jasper7
В Realm существует такая возможность как MigrationCallback. Описываете свой метод, ему на вход подается объект Migration, который содержит новый и старый объекты Realm. Далее вы уже сами производите необходимые операции, чтобы преобразовать данные из старого формата в новый.
Отличный пример из документации:
Ситуация когда поля LastName, FirstName заменили на единственное FullName, достаточно запустить такой цикл:
Подробнее о миграции можно почитать тут:
Atreides07
Похоже ссылка о миграции отвалилась. Это получается что в модели надо хранить и старые и новые поля одновременно?
Jasper7
Нет, старые поля будут недоступны, потому что в новой модели они не описаны, и Realm предполагаю удалит из базы все поля(после успешной миграции), которые не описаны в модели,
Atreides07
Спасибо. Гораздо понятнее стало с Realm. А был опыт использования F# + Realm ?
Jasper7
Я пишу только на C#
Schvepsss
UPD к комментарию.
Подробнее о миграции можно почитать тут.
Подробнее о миграции можно почитать тут.
Atreides07
Спасибо. В принципе все понятно.