Как я могу использовать MVVM с компонентами пользовательского интерфейса приложения / действия и AsyncTask - PullRequest
8 голосов
/ 21 июня 2020

Насколько я знаю, ViewModel должен быть изолирован от UI / View и содержит только logi c, который наблюдает за данными, поступающими с сервера или базы данных

В моем приложении я использовал REST API "retrofit" и API blogger, и я попытался перенести / обновить текущий код до MVVM, но есть несколько проблем, давайте go к коду

BloggerAPI Class

    public class BloggerAPI {

    private static final String BASE_URL =
            "https://www.googleapis.com/blogger/v3/blogs/4294497614198718393/posts/";

    private static final String KEY = "the Key";
    private PostInterFace postInterFace;
    private static BloggerAPI INSTANCE;

    public BloggerAPI() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
         postInterFace = retrofit.create(PostInterFace.class);
    }

    public static String getBaseUrl() {
        return BASE_URL;
    }

    public static String getKEY() {
        return KEY;
    }

    public static BloggerAPI getINSTANCE() {
        if(INSTANCE == null){
            INSTANCE = new BloggerAPI();
        }
        return INSTANCE;
    }

    public interface PostInterFace {
        @GET
        Call<PostList> getPostList(@Url String url);
    }

    public Call<PostList>getPosts(String url){
        return postInterFace.getPostList(url);
    }
}

this getData метод, который я использовал в Mainctivity для получения сообщений в блоге

public void getData() {
    if (getItemsByLabelCalled) return;
    progressBar.setVisibility(View.VISIBLE);

    String url = BloggerAPI.getBaseUrl() + "?key=" + BloggerAPI.getKEY();

    if (token != "") {
        url = url + "&pageToken=" + token;
    }
    if (token == null) {
        return;
    }

    final Call<PostList> postList = BloggerAPI.getINSTANCE().getPosts(url);
    postList.enqueue(new Callback<PostList>() {
        @Override
        public void onResponse(@NonNull Call<PostList> call, @NonNull Response<PostList> response) {
            if (response.isSuccessful()) {
                progressBar.setVisibility(View.GONE);
                PostList list = response.body();
                Log.d(TAG, "onResponse: " + response.body());
                if (list != null) {
                    token = list.getNextPageToken();
                    items.addAll(list.getItems());
                    adapter.notifyDataSetChanged();

                    for (int i = 0; i < items.size(); i++) {
                        items.get(i).setReDefinedID(i);
                    }

                    if (sqLiteItemsDBHelper == null || sqLiteItemsDBHelper.getAllItems().isEmpty()) {
                        SaveInDatabase task = new SaveInDatabase();
                        Item[] listArr = items.toArray(new Item[0]);
                        task.execute(listArr);
                    }
                }

            } else {
                progressBar.setVisibility(View.GONE);
                recyclerView.setVisibility(View.GONE);
                emptyView.setVisibility(View.VISIBLE);

                int sc = response.code();
                switch (sc) {
                    case 400:
                        Log.e("Error 400", "Bad Request");
                        break;
                    case 404:
                        Log.e("Error 404", "Not Found");
                        break;
                    default:
                        Log.e("Error", "Generic Error");
                }
            }
        }

        @Override
        public void onFailure(@NonNull Call<PostList> call, @NonNull Throwable t) {
            Toast.makeText(MainActivity.this, "getData error occured", Toast.LENGTH_LONG).show();
            Log.e(TAG, "onFailure: " + t.toString());
            Log.e(TAG, "onFailure: " + t.getCause());
            progressBar.setVisibility(View.GONE);
            recyclerView.setVisibility(View.GONE);
            emptyView.setVisibility(View.VISIBLE);
        }
    });

}

Я создал PostsViewModel , чтобы практически подумать, как выполнить миграцию текущий код для использования MVVM

   public class PostsViewModel extends ViewModel {

   public MutableLiveData<PostList> postListMutableLiveData = new MutableLiveData<>();

    public void getData() {
        String token = "";
//        if (getItemsByLabelCalled) return;
//        progressBar.setVisibility(View.VISIBLE);

        String url = BloggerAPI.getBaseUrl() + "?key=" + BloggerAPI.getKEY();

        if (token != "") {
            url = url + "&pageToken=" + token;
        }
        if (token == null) {
            return;
        }

        BloggerAPI.getINSTANCE().getPosts(url).enqueue(new Callback<PostList>() {
            @Override
            public void onResponse(Call<PostList> call, Response<PostList> response) {
                postListMutableLiveData.setValue(response.body());
            }

            @Override
            public void onFailure(Call<PostList> call, Throwable t) {

            }
        });

    }
}

и он используется таким образом в MainActivity

 postsViewModel =  ViewModelProviders.of(this).get(PostsViewModel.class);

        postsViewModel.postListMutableLiveData.observe(this, postList -> {
            items.addAll(postList.getItems());
            adapter.notifyDataSetChanged();
        });

теперь есть две проблемы при использовании этого способа MVVM "ViewModel"

  1. сначала в текущем методе getData в MainActivity, он содержит некоторые компоненты, которые должны работать только на уровне просмотра, например, список элементов, recyclerView должен установить View.GONE в случае неудачного ответа sful, progressBar, emptyView TextView, адаптер, который должен уведомлять, есть ли изменения в списке, и, наконец, мне нужен контекст для создания сообщений Toast.

Думаю, что для решения этой проблемы чтобы добавить компоненты пользовательского интерфейса и другие вещи в класс ViewModel и создать такой конструктор

public class PostsViewModel extends ViewModel {

    Context context;
    List<Item> itemList;
    PostAdapter postAdapter;
    ProgressBar progressBar;
    TextView textView;

    public PostsViewModel(Context context, List<Item> itemList, PostAdapter postAdapter, ProgressBar progressBar, TextView textView) {
        this.context = context;
        this.itemList = itemList;
        this.postAdapter = postAdapter;
        this.progressBar = progressBar;
        this.textView = textView;
    }

, но это логически не связано с архитектурой MVVM и наверняка вызывает утечку памяти, также я не смогу создать экземпляр ViewModel обычным способом, например

 postsViewModel =  ViewModelProviders.of(this).get(PostsViewModel.class);

        postsViewModel.postListMutableLiveData.observe(this, postList -> {
            items.addAll(postList.getItems());
            adapter.notifyDataSetChanged();
        });

и должен использоваться следующим образом

postsViewModel = new PostsViewModel(this,items,adapter,progressBar,emptyView);

, поэтому первый вопрос: как связать эти компоненты пользовательского интерфейса с ViewModel?

секунда в текущем getata Я использовал класс SaveInDatabase, использую AsyncTask способ сохранить все элементы в базе данных SQLite , второй вопрос: как переместить этот класс для работы с ViewModel? но он также должен работать на уровне просмотра, чтобы избежать утечки

SaveInDatabase Class

    static class SaveInDatabase extends AsyncTask<Item, Void, Void> {

        @Override
        protected Void doInBackground(Item... items) {
            List<Item> itemsList = Arrays.asList(items);
//            runtimeExceptionDaoItems.create(itemsList);

            for (int i = 0 ; i< itemsList.size();i++) {
                sqLiteItemsDBHelper.addItem(itemsList.get(i));
                Log.e(TAG, "Size :" + sqLiteItemsDBHelper.getAllItems().size());
            }


            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
        }
    }

Ответы [ 2 ]

1 голос
/ 30 июня 2020

Поскольку ваш вопрос немного широк, я не даю никакого исходного кода для него, а просто упоминаю образцы, которые явно решают проблемы, упомянутые с MVVM .

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

Прежде всего необходимо реструктурировать архитектуру приложения, чтобы каждый уровень имел назначенную роль в MVVM. Вы можете следовать следующему шаблону для того же:

  1. Только модель представления будет иметь доступ к уровню пользовательского интерфейса
  2. Модель представления будет соединена со слоем варианта использования
  3. Вариант использования слой будет соединяться с уровнем данных
  4. Ни один слой не будет иметь cycli c ссылки на другие компоненты.
  5. Итак, теперь для базы данных репозиторий решит, из какого раздела следует извлечь данные
  6. Это может быть либо из сети, либо из базы данных.

Все эти точки (кроме части базы данных) охватываются Средняя статья , где каждый шаг покрывается фактические API. Наряду с этим модульным тестом также распространяется действие.

В этом проекте используются библиотеки

  1. Coroutines
  2. Модернизация
  3. Коин (внедрение зависимостей) Может быть заменен на dagger2 требуется
  4. MockWebServer (тестирование)
  5. Язык: Kotlin

Полная Исходный код можно найти на Github

Edit

Kotlin - официальный поддерживаемый язык для Android Development. Я предлагаю вам перенести свои проекты java android на Kotlin.

Еще для преобразования Kotlin в Java, Go в Menu> Tools> Kotlin> Decompile Kotlin to Java Опция

1 голос
/ 25 июня 2020

На самом деле вопрос слишком широкий, чтобы ответить, потому что есть много способов реализовать для этого случая. Прежде всего, никогда не передавайте объекты просмотра в viewModel. ViewModel используется для уведомления об изменении уровня пользовательского интерфейса с помощью LiveData или rx Java без сохранения экземпляра представления. Вы можете попробовать этот способ.

    class PostViewModel extends ViewModel {

    private final MutableLiveData<PostList> postListLiveData = new MutableLiveData<PostList>();
    private final MutableLiveData<Boolean> loadingStateLiveData = new MutableLiveData<Boolean>();
    private String token = "";

    public void getData() {
        loadingStateLiveData.postValue(true);
      
    //        if (getItemsByLabelCalled) return;
    //        progressBar.setVisibility(View.VISIBLE);

        String url = BloggerAPI.getBaseUrl() + "?key=" + BloggerAPI.getKEY();

        if (token != "") {
            url = url + "&pageToken=" + token;
        }
        if (token == null) {
            return;
        }

        BloggerAPI.getINSTANCE().getPosts(url).enqueue(new Callback<PostList>() {
            @Override
            public void onResponse(Call<PostList> call, Response<PostList> response) {
                loadingStateLiveData.postValue(false);
                postListLiveData.setValue(response.body());
                token = response.body().getNextPageToken(); //===> the token

            }

            @Override
            public void onFailure(Call<PostList> call, Throwable t) {
                loadingStateLiveData.postValue(false);
            }
        });

    }

    public LiveData<PostList> getPostListLiveData(){
        return postListLiveData;
    }

    public LiveData<Boolean> getLoadingStateLiveData(){
        return loadingStateLiveData;
    }
}

, и вы можете наблюдать изменения в своей деятельности, как это.

postsViewModel = ViewModelProviders.of(this).get(PostsViewModel.class);
    postsViewModel.getPostListLiveData().observe(this,postList->{
        if(isYourPostListEmpty(postlist)) {
            recyclerView.setVisibility(View.GONE);
            emptyView.setVisibility(View.VISIBLE);
            items.addAll(postList.getItems());
            adapter.notifyDataSetChanged();
        }else {
            recyclerView.setVisibility(View.VISIBLE);
            emptyView.setVisibility(View.GONE);

        }
    });


    postsViewModel.getLoadingStateLiveData().observe(this,isLoading->{
        if(isLoading) {
            progressBar.setVisibility(View.VISIBLE);
        }else {
            progressBar.setVisibility(View.GONE);
        }
    });

Лично я предпочитаю использовать Enum для обработки ошибок, но Я не могу публиковать здесь сообщения, так как это сделает ответ очень длинным. Для второго вопроса используйте Room из Google. Это сделает вашу жизнь намного проще. Он очень хорошо работает с mvvm и изначально поддерживает liveData. Вы можете попробовать CodeLab из Google, чтобы попрактиковаться в использовании комнаты.

Бонус : вам не нужно редактировать URL-адрес следующим образом:

String url = BloggerAPI.getBaseUrl() + "?key=" + BloggerAPI.getKEY();

    if (token != "") {
        url = url + "&pageToken=" + token;
    }

Вы можете использовать @ Path или @ query в зависимости от ваших требований.

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