- PVSM.RU - https://www.pvsm.ru -

Наблюдаемые модели в Realm Xamarin

Если вас раздражает аспект обновления данных, хранящихся в моделях, и вы думаете о том, как было бы здорово, если бы модель могла быть более самостоятельной и уведомлять об изменениях, добро пожаловать под кат.

Наблюдаемые модели в Realm Xamarin - 1

Примечание. Далее повествование будет вестись от имени автора.

Будучи разработчиком .NET, я неравнодушен к MVVM и привязке данных. Благодаря им я могу отвязать представления от логики приложения, и, в большинстве случаев, писать приложение без необходимости беспокоиться об обновлении пользовательского интерфейса. Однако, остается один раздражающий аспект — обновление данных, хранящихся в моделях. Чаще всего я получаю сетевой вызов, который принимает определенные данные, сохраняет их на диске, а затем обновляет модели представлений посредством обертывания соответствующей модели, будь то путем сравнения изменений или же обновления всего интерфейса в целом. Но ведь было бы здорово, если модель могла самостоятельно об этом позаботиться и уведомить нас об изменениях, не правда ли? Оказывается, при использовании Realm [1] для долговременного хранения данных это становится возможным.

Это может стать неожиданностью для некоторых, но все объекты, хранящиеся в Realm, изначально доступны для наблюдения. И в том числе даже функциональные зависимости и результаты запроса! И благодаря этому их необычайно легко передавать непосредственно к привязке данных. В этом посте я сосредоточусь на Xamarin.Forms, поскольку они поставляются с дружелюбным по отношению к привязке данных механизмом визуализации, но вы можете запросто использовать проект с нативным UI с такими фреймворкоми, как MvvmCross [2] и MVVM Light [3]. Итак, давайте рассмотрим на примере, как, используя 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 [4], реализует INotifyCollectionChanged [5], за которым следит механизм привязки данных Xamarin Forms, так что интерфейс получает уведомления о любых изменениях, происходящих в коллекции. При работе с нативным проектом UI можно либо преобразовать этот тип самостоятельно, или же использовать метод расширения AsRealmCollection [6]. Теперь в подтверждение моих слов давайте рассмотрим, каким образом мы сможем выполнять определенные изменения.

Редактирование единственного контакта

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 на главном экране связан с «активным» запросом, он будет обновлять соответствующие новые данные уже при нажатии на кнопку «назад». Подключать навигацию к моделям представления не так уж и весело, поэтому я не стану здесь на этом останавливаться. Но с одним из способов это сделать можно ознакомиться в готовом примере [7].

Наблюдаемые модели в Realm Xamarin - 2

Добавление контакта в избранное

Давайте немного расширим функциональность нашего пока что «сырого» приложения, то есть, добавим возможность отметить контакт в качестве избранного. Сначала добавляем новое свойство нашему 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);
        });
    }
}

Благодаря этому избранные контакты поднимутся вверх, но притом обе группы будут все так же распределяться в алфавитном порядке. И наконец, давайте добавим к нашему элементу кнопку ☆ (если вас интересует чередование кнопок ☆ и ★ в зависимости от того, добавлен ли контакт в избранное или нет, ознакомьтесь с готовым примером [7]):

<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>

Вот и все. Ничего сверхъестественного, конечно, но таким образом мы смогли наглядно продемонстрировать, до какой степени проще добавлять новые функции, когда модели доступны для наблюдения и постоянно обновляются.

Наблюдаемые модели в Realm Xamarin - 3

Возможные последствия для производительности

Этот вопрос постоянно всплывает, когда речь заходит о наблюдаемых и активных объектах — «Как они влияют на производительность?» Разумеется, ничего не достается бесплатно. И возможность наблюдения за изменениями не является исключением. Так что за использование этой функции приходится расплачиваться небольшой надбавкой в ресурсопотреблении. Хорошая новость заключается в том, что это наблюдение за изменением случается лишь тогда, когда вы подписываетесь на PropertyChanged или CollectionChanged и останавливается, как только подписка прекращается. Это означает, что если у вас обрабатывается миллион объектов и вы не желаете получать о них уведомления, то возможности наблюдения RealmObjects не будут никоим образом оказывать на вас влияние. И если вы заботитесь об изменениях и используйте RealmObjects для привязки данных, то замедление, вызванное уведомлениями, будет ничтожно малым по сравнению с логикой вычислений привязки данных и макета, которые выполняются Xamarin Forms при каждом обновлении.

Заключение

Realm позволяет с легкостью разрабатывать пользовательский интерфейс приложения на основе данных, которые имеются на диске. Благодаря этому можно разрабатывать по-настоящему емкие в автономном плане приложения. А также, что более важно, создавать пользовательский интерфейс, который будет независимым от того, где возникают изменения модели. Они могут быть результатом действия пользователя, веб-запроса или даже синхронизации с сервером через Realm Mobile Platform [8] (который очень скоро [9] присоединится к сервисам Xamarin). И неважно каким именно результатом они будут — пользователь увидит каждое обновление, как только оно появится.

Если у Вас есть в запасе несколько минут, загляните и заберите базу данных [10] (она создана на базе открытого исходного кода и доступна бесплатно). Или если вы похожи на меня и любите поиграть с готовым проектом, прежде чем читать сообщения в блоге, возьмите код на GitHub [7].

Благодарим за перевод

Наблюдаемые модели в Realm Xamarin - 4Александр Алексеев [11] — Xamarin-разработчик, фрилансер. Работает с .NET-платформой с 2012 года. Участвовал в разработке системы автоматизации закупок в компании Digamma. C 2015 года ушел во фриланс и перешел на мобильную разработку с использованием Xamarin. В текущее время работает в компании StecPoint над iOS приложением.

Ведет ресурс XamDev.ru [12] и сообщества «Xamarin Developers» в социальных сетях: VK [13], Facebook [14], Telegram [15].

Другие статьи из нашего блога о Xamarin читайте по ссылке #xamarincolumn [16].

Автор: Microsoft

Источник [17]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/c-2/237088

Ссылки в тексте:

[1] Realm: https://aka.ms/habr_320338_1

[2] MvvmCross: https://aka.ms/habr_320338_2

[3] MVVM Light: https://aka.ms/habr_320338_3

[4] Realm.All: https://aka.ms/habr_320338_4

[5] INotifyCollectionChanged: https://aka.ms/habr_320338_5

[6] AsRealmCollection: https://aka.ms/habr_320338_6

[7] готовом примере: https://aka.ms/habr_320338_7

[8] Realm Mobile Platform: https://aka.ms/habr_320338_8

[9] очень скоро: https://aka.ms/habr_320338_9

[10] заберите базу данных: https://aka.ms/habr_320338_10

[11] Александр Алексеев: https://aka.ms/habr_320338_11

[12] XamDev.ru: https://aka.ms/habr_320338_11/

[13] VK: https://aka.ms/vk_xamarin_developers

[14] Facebook: https://aka.ms/fb_xamdev

[15] Telegram: https://aka.ms/telegram_xamarin_russia

[16] #xamarincolumn: https://habrahabr.ru/search/?target_type=posts&q=%5Bxamarincolumn%5D&order_by=date

[17] Источник: https://habrahabr.ru/post/320338/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best