如何将 MVVM 与 App/activity 和 AsyncTask 的 UI 组件一起使用

Dr *_*ido 7 java android mvvm android-asynctask retrofit

据我所知,ViewModel 应该与 UI/View 隔离,并且只包含观察来自服务器或数据库的数据的逻辑

在我的应用程序中,我使用了 REST API“改造”和博主 API,并尝试将当前代码迁移/升级到 MVVM,但存在一些问题,让我们转到代码

BloggerAPI 类

    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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我在 Mainctivity 中使用的这个getData方法来检索博客文章

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);
        }
    });

}
Run Code Online (Sandbox Code Playgroud)

我创建了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) {

            }
        });

    }
}
Run Code Online (Sandbox Code Playgroud)

它因此在 MainActivity 中使用

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

        postsViewModel.postListMutableLiveData.observe(this, postList -> {
            items.addAll(postList.getItems());
            adapter.notifyDataSetChanged();
        });
Run Code Online (Sandbox Code Playgroud)

现在使用这种MVVM“ViewModel”方式存在两个问题

  1. 首先在MainActivity的当前getData方法中,它包含一些应该只在 View 层工作的组件,如项目列表,recyclerView 需要设置View.GONE以防响应不成功,progressBar,emptyView TextView,需要通知的适配器是列表中的更改,最后我需要上下文来使用创建 Toast 消息。

为了解决这个问题,我想将 UI 组件和其他东西添加到 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;
    }
Run Code Online (Sandbox Code Playgroud)

但这在逻辑上与 MVVM arch 不合逻辑,并且肯定会导致内存泄漏,我也将无法像这样以常规方式创建 ViewModel 的实例

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

        postsViewModel.postListMutableLiveData.observe(this, postList -> {
            items.addAll(postList.getItems());
            adapter.notifyDataSetChanged();
        });
Run Code Online (Sandbox Code Playgroud)

并且必须像这样使用

postsViewModel = new PostsViewModel(this,items,adapter,progressBar,emptyView);
Run Code Online (Sandbox Code Playgroud)

所以第一个问题是如何将这些 UI 组件与 ViewModel 绑定?

  1. 在当前getata中的第二个我使用 SaveInDatabase 类使用AsyncTask方式保存SQLite数据库中的所有项目第二个问题是如何移动这个类以使用 ViewModel?但它也需要在View层工作,以避免泄漏

SaveInDatabase

    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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

WHO*_*HOA 1

实际上这个问题太宽泛了,无法回答,因为这种情况有很多实现方法。首先,永远不要将视图对象传递给 viewModel。ViewModel 用于通过 LiveData 或 rxJava 通知 ui 层的更改,而不保留视图实例。你可以试试这个方法。

    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;
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以像这样观察你的活动的变化。

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);
        }
    });
Run Code Online (Sandbox Code Playgroud)

就我个人而言,我喜欢使用 Enum 进行错误处理,但我不能在这里发布,因为它会使答案很长。对于第二个问题,请使用Google 的Room 。它会让你的生活变得更加轻松。它与 mvvm 配合得很好,并且本身支持 liveData。您可以尝试使用 google 的CodeLab来练习使用 room。

额外好处:您不需要像这样编辑网址:

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

    if (token != "") {
        url = url + "&pageToken=" + token;
    }
Run Code Online (Sandbox Code Playgroud)

您可以根据您的要求使用@Path@query 。