Спостережувані моделі в Realm Xamarin

Якщо вас дратує аспект оновлення даних, що зберігаються в моделях, і ви думаєте про те, як було б здорово, якщо б модель могла бути більш самостійною і повідомляти про зміни, ласкаво просимо під кат.



Примітка. Далі розповідь буде вестися від імені автора.

Будучи розробником .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.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.