MVVM в Android, доступ к assetManager, не нарушая шаблон - PullRequest
0 голосов
/ 03 февраля 2019

У меня есть файл JSON в папке ресурсов, и он нужен классу DataManager (репозиторий), чтобы assetManager (и контекст) имели доступ к ресурсам.

Проблема заключается в том, что, основываясь на передовом опыте, контекст Android или специфичный для Android код не должны передаваться в слой данных (ViewModel-Repo-Model) из-за простого написания модульных тестов и т. Д., А также представления не должны взаимодействоватьнепосредственно со слоем данных.

Я закончил тем, что предоставил список, используя и внедрив его в хранилище.

Правильно ли это сделать?

-Спасибо

PS: мой класс Module, который предоставляет список

@Module
public class UtilModule {

    @Provides
    @JsonScope
    JsonUtil provideJsonUtil(AssetManager assetManager){
        return new JsonUtil(assetManager);
    }

    @Provides
    @JsonScope
    String provideJson(JsonUtil util){
        return util.getJson();
    }

    @Provides
    @JsonScope
    Type provideType(){
        return new TypeToken<List<Data>>() {}.getType();
    }
    @Provides
    @JsonScope
    DataManager provideDataManager (Gson gson, Type type,String json) {
        return new DataManager (gson.fromJson(json, type));
    }
}

Ответы [ 2 ]

0 голосов
/ 21 апреля 2019

Это не нарушение MVVM для ViewModel и / или Repository для прямого доступа к контексту Application, и это все, что вам нужно для доступа к AssetsManager.Вызов Application.getAssets() в порядке, потому что ViewModel не использует какой-либо конкретный контекст Activity.

Например, вы можете использовать предоставленный Google AndroidViewModel подклассвместо суперкласса ViewModel.AndroidViewModel занимает Application в своем конструкторе (ViewModelProviders вставит его для вас).Вы можете передать Application своему Repository в его конструкторе.

В качестве альтернативы, вы можете использовать Dagger инъекцию зависимостей, чтобы внедрить Application непосредственно в Repository.(Внедрить контекст Application немного сложно. См. Dagger 2, вводящий контекст Android и , эта проблема подана в репозиторий Danger github .) Если вы хотите сделать его действительно гладким,Вы можете настроить провайдера для AssetManager и добавить его непосредственно в Repository.

Наконец, если вы используете Room и все, что вам нужно, это предварительно заполнить базу данных Room предварительно сконфигурированнойБаза данных хранится в активах, вы можете следовать инструкциям здесь: Как использовать Room Persistence Library с предварительно заполненной базой данных?

0 голосов
/ 04 февраля 2019

Поскольку вы используете MVVM впервые, мы можем постараться сделать все просто.

[ Просмотр Компонент C] ---- ( наблюдает ) [ ViewModel Компонент B] ---- [ Репозиторий]

В соответствии с правилом разделения проблем ViewModel должен предоставлять LiveData.LiveData использует Observers для наблюдения за изменениями данных.Целью ViewModel является отделение слоя данных от пользовательского интерфейса.ViewModel не должен знать о классах платформы Android.

В архитектуре MVVM роль ViewModel заключается в получении данных из репозитория.Вы можете либо сохранить свой json-файл в качестве локального источника данных, используя Room, либо сохранить Json API в качестве удаленного источника данных.В любом случае, общая реализация выглядит следующим образом:

Компонент A - объект (реализует ваши методы получения и установки)

Метод 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 - представление (UI Controller)

Используете ли вы метод 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 singleton создавался с использованием метода 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)

ДругойДело в том, что независимо от того, выбрали ли вы действие или фрагмент для своего пользовательского интерфейса, вы будете использовать ViewModelProviders.of для сохранения вашей ViewModel, когда область действия действия или фрагмента активна. Если вы реализуете различные действия / фрагменты, у вас будут разныеэкземпляры ViewModel в вашем приложении.

Еслинапример, вы реализуете свою базу данных с помощью Room и хотите, чтобы ваш пользователь мог обновлять вашу базу данных во время использования вашего приложения, теперь вашему приложению может потребоваться один и тот же экземпляр ViewModel для всей вашей основной деятельности и операции обновления.Хотя это анти-шаблон, ViewModel предоставляет простую фабрику с пустым конструктором.Вы можете реализовать его в Room, используя 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.

Эта статья в разделе «Постоянные данные» от Android может быть более полезной, чем мои комментарии, если вы хотите узнать больше о затратах на производительность.

...