Поскольку вы используете MVVM впервые, мы можем попытаться упростить ситуацию.
[ View Компонент C] ---- (наблюдает) [ ViewModel Компонент B ] ---- [ Репозиторий ]
Согласно правилу разделения интересов ViewModel должна предоставлять LiveData. LiveData использует наблюдателей для наблюдения за изменениями данных. Целью ViewModel является отделение уровня данных от пользовательского интерфейса. ViewModel не должен знать о классах фреймворка Android.
В архитектуре MVVM роль ViewModel заключается в извлечении данных из репозитория. Вы можете либо сохранить файл json в качестве локального источника данных с помощью Room, либо сохранить Json API в качестве удаленного источника данных. В любом случае, общая реализация выглядит следующим образом:
Компонент A — Entity (реализует геттеры и сеттеры)
Способ 1. Использование комнаты
@Entity(tableName = "file")
public class FileEntry{
@PrimaryKey(autoGenerate = true)
private int id;
private String content; // member variables
public FileEntry(String content){ // constructor
this.id = id;
this.content = content;
}
public int getId(){ // getter methods
return id;
}
public void setId(int id){ // setter methods
this.id = id;
}
public String getContent(){
return content;
}
public void setContent(String content){
this.content = content;
}
}
Способ 2. Использование удаленного источника данных
public class FileEntry implements Serializable{
public String getContent(){
return content;
}
private String content;
}
Компонент B — ViewModel (уровень представления)
Способ 1. Использование комнаты
Поскольку вы спросили о том, как можно передать контекст Android, вы можете сделать это, расширив AndroidViewModel, как показано ниже, чтобы включить ссылку на приложение. Это если для вашей базы данных требуется контекст приложения, но общее правило заключается в том, что Activity & Fragments не должны храниться в ViewModel.
Предположим, у вас есть «файлы» в качестве переменной-члена, определенной для вашего списка объектов, скажем, в этом случае, объектов «FileEntry»:
public class FileViewModel extends AndroidViewModel{
// Wrap your list of FileEntry objects in LiveData to observe data changes
private LiveData<List<FileEntry>> files;
public FileViewModel(Application application){
super(application);
FilesDatabase db = FilesDatabase.getInstance(this.getApplication());
Способ 2. Использование удаленного источника данных
public class FileViewModel extends ViewModel{
public FileViewModel(){}
public LiveData<List<FileEntry>> getFileEntries(String content){
Repository repository = new Repository();
return repository.getFileEntries(content);
}
}
В этом случае метод getFileEntries содержит MutableLiveData:
final MutableLiveData<List<FileEntry>> mutableLiveData = new MutableLiveData<>();
Если вы реализуете с помощью клиента Retrofit, вы можете сделать что-то похожее на приведенный ниже код, используя асинхронные обратные вызовы. Код взят из Руководства по модернизации 2 в Future Studio с некоторыми изменениями для этого примера обсуждения.
// asynchronous
call.enqueue(new Callback<ApiData>() {
@Override
public void onResponse(Call<ApiData> call, Response<ApiData> response) {
if (response.isSuccessful()) {
mutableLiveData.setValue(response.body().getContent());
} else {
int statusCode = response.code();
// handle request errors yourself
ResponseBody errorBody = response.errorBody();
}
}
@Override
public void onFailure(Call<ApiData> call, Throwable t) {
// handle execution failures like no internet connectivity
}
return mutableLiveData;
Компонент C — представление (контроллер пользовательского интерфейса)
Независимо от того, используете ли вы метод 1 или 2, вы можете сделать следующее:
FileViewModel fileViewModel = ViewModelProviders.of(this).get(FileViewModel.class);
fileViewModel.getFileEntries(content).observe(this, fileObserver);
Надеюсь, это полезно.
Влияние на производительность
На мой взгляд, решение о том, использовать ли какой метод, может зависеть от того, сколько вызовов данных вы реализуете. Если несколько, Retrofit может быть лучшей идеей для упрощения вызовов API. Если вы реализуете его с помощью клиента Retrofit, у вас может быть что-то похожее на приведенный ниже код, взятый из этой ссылки статья о Android-руководстве по архитектуре приложений:
public LiveData<User> getUser(int userId) {
LiveData<User> cached = userCache.get(userId);
if (cached != null) {
return cached;
}
final MutableLiveData<User> data = new MutableLiveData<>();
userCache.put(userId, data);
webservice.getUser(userId).enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
data.setValue(response.body());
}
});
return data;
}
Приведенная выше реализация может иметь преимущества в производительности потоков, поскольку Retrofit позволяет выполнять асинхронные сетевые вызовы с использованием enqueue
и возвращать метод onResponse
в фоновом потоке. Используя метод 2, вы можете использовать шаблон обратного вызова Retrofit для сетевых вызовов в параллельных фоновых потоках, не мешая основному потоку пользовательского интерфейса.
Еще одним преимуществом вышеприведенной реализации является то, что если вы делаете несколько вызовов данных API, вы можете чисто получить ответ через интерфейс webservice
выше для ваших LiveData. Это позволяет нам передавать ответы между различными источниками данных. Затем вызов data.setValue
устанавливает значение MutableLiveData, а затем отправляет его активным наблюдателям в основном потоке в соответствии с документацией Android.
Если вы уже знакомы с SQL и реализуете только 1 базу данных, выбор библиотеки сохранения состояния комнаты может быть хорошим вариантом. Он также использует ViewModel, что дает преимущества в производительности, поскольку снижается вероятность утечек памяти, поскольку ViewModel поддерживает меньше сильных ссылок между вашим пользовательским интерфейсом и классами данных.
Одной из проблем может быть ваш репозиторий БД (например, FilesDatabase
, реализованный как синглтон, чтобы обеспечить единую глобальную точку доступа, используя общедоступный статический метод для создания экземпляра класса, чтобы был открыт только 1 один и тот же экземпляр БД в любой момент? Если да, синглтон может быть привязан к области приложения, и если пользователь все еще запускает приложение, может произойти утечка ViewModel. Таким образом, убедитесь, что ваша ViewModel использует LiveData для ссылки на представления. Кроме того, это может быть полезно использовать ленивую инициализацию, чтобы новый экземпляр одноэлементного класса FilesDatabase
создавался с использованием метода getInstance
, если предыдущие экземпляры еще не созданы:
private static FilesDatabase dbInstance;
// Synchronized may be an expensive operation but ensures only 1 thread runs at a time
public static synchronized FilesDatabase getInstance(Context context) {
if (dbInstance == null) {
// Creates the Room persistent database
dbInstance = Room.databaseBuilder(context.getApplicationContext(), FilesDatabase.class, FilesDatabase.DATABASE_NAME)
Другое дело, что независимо от вашего выбора Activity или Fragment для вашего пользовательского интерфейса вы будете использовать ViewModelProviders.of
для сохранения своей ViewModel, пока область действия вашего Activity или Fragment жива. Если вы реализуете разные действия/фрагменты, в вашем приложении будут разные экземпляры ViewModel.
Если, например, вы реализуете свою базу данных с помощью Room и хотите, чтобы ваш пользователь обновлял вашу базу данных при использовании вашего приложения, вашему приложению теперь может потребоваться один и тот же экземпляр ViewModel для вашей основной деятельности и действия по обновлению. Несмотря на антишаблон, ViewModel предоставляет простую фабрику с пустым конструктором. Вы можете реализовать это в комнате, используя public class UpdateFileViewModelFactory extends ViewModelProvider.NewInstanceFactory{
:
@Override
public <T extends ViewModel> T create(@NotNull Class<T> modelClass) {
return (T) new UpdateFileViewModel(sDb, sFileId);
Выше T — это параметр типа create. В приведенном выше фабричном методе класс T расширяет ViewModel. Переменная-член sDb предназначена для FilesDatabase, а sFileId — для идентификатора int, который представляет каждый FileEntry.
Эта статья о разделе Persist Data от Android может быть полезнее моей комментарии, если вы хотите узнать больше о затратах на производительность.
person
B4eight
schedule
04.02.2019