简体   繁体   English

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

[英]How can I use MVVM with the UI components of the App/activity and AsyncTask

As I know that the ViewModel should be secluded from the UI/View and contains only the logic that observes the data that's coming from the server or database据我所知,ViewModel 应该与 UI/View 隔离开来,并且只包含观察来自服务器或数据库的数据的逻辑

In my App, I used REST API "retrofit" and blogger API and I tried to migrate/upgrade the current code to MVVM but there are a few problems, let's go to the code In my App, I used REST API "retrofit" and blogger API and I tried to migrate/upgrade the current code to MVVM but there are a few problems, let's go to the code

BloggerAPI Class 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 method I used in the Mainctivity to retrieve blog posts我在 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);
        }
    });

}

I created the PostsViewModel to trying to think practically how to migrate the current code to use MVVM我创建了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) {

            }
        });

    }
}

and it's used thus in MainActivity因此在 MainActivity 中使用它

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

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

now there are two problems using this way of MVVM "ViewModel"现在使用这种方式的MVVM“ViewModel”有两个问题

  1. first in the current getData method in the MainActivity it's contains some components that should work only in the View layer like the items list, the recyclerView needs to set View.GONE in case of response unsuccessful, progressBar, emptyView TextView, the adapter that needs to notify if there are changes in the list, and finally I need the context to used the create the Toast messages.首先在 MainActivity 的当前getData方法中,它包含一些只能在 View 层工作的组件,如项目列表,recyclerView 需要设置View.GONE以防响应不成功,progressBar,emptyView TextView,需要的适配器通知列表中是否有更改,最后我需要上下文来使用创建 Toast 消息。

To solve this issue I think to add the UI components and other things into the ViewModel Class and create a constructor like this为了解决这个问题,我认为将 UI 组件和其他内容添加到 ViewModel Class 并创建一个像这样的构造函数

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

but this is not logically with MVVM arch and for sure cause memory leaking also I will not be able to create the instance of ViewModel with regular way like this但这在逻辑上与 MVVM 架构不符,并且肯定会导致 memory 泄漏,我也将无法以这样的常规方式创建 ViewModel 的实例

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

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

and must be used like this并且必须像这样使用

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

so the first question is How to bind these UI components with the ViewModel?所以第一个问题是如何将这些 UI 组件与 ViewModel 绑定?

  1. second in the current getata I used the SaveInDatabase class use the AsyncTask way to save all items in the SQLite database the second question is How to move this class to work with ViewModel?在当前getata中的第二个我使用了 SaveInDatabase class 使用AsyncTask方式保存SQLite数据库中的所有项目第二个问题是如何移动这个 class 以使用 ViewModel? but it also needs to work in the View layer to avoid leaking但它也需要在 View 层工作以避免泄漏

the SaveInDatabase Class 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);
        }
    }

Actually the question is too broad to answer because there are many ways to implement for this case.实际上这个问题太宽泛而无法回答,因为这种情况有很多方法可以实现。 First of all, never pass view objects to viewModel.首先,永远不要将视图对象传递给 viewModel。 ViewModel is used to notify changes to ui layer with LiveData or rxJava without retaining the view instance. ViewModel 用于通过 LiveData 或 rxJava 通知 ui 层的更改,而不保留视图实例。 You may try this way.你可以试试这个方法。

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

and you may observe the changes from your activity like this.你可以像这样观察你的活动的变化。

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

For my personal prefer, I like using Enum for error handling, but I can't post here as it will make the answer very long.对于我个人的偏好,我喜欢使用 Enum 进行错误处理,但我不能在这里发布,因为它会使答案变得很长。 For your second question, use Room from google.对于第二个问题,请使用 Google 的Room It will make you life a lot easier.它会让你的生活轻松很多。 It work very well with mvvm and it natively support liveData.它与 mvvm 一起工作得很好,并且它原生支持 liveData。 You can try CodeLab from google to practise using room.你可以试试谷歌的CodeLab来练习使用房间。

Bonus : You don't need to edit the url like this:奖励:您不需要像这样编辑 url :

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

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

You can use @Path or @query based on your requirements.您可以根据需要使用@Path@query

As your question is bit broad, I am not giving any source code for the same, Rather mentioning samples which clearly resolves issues mentioned with MVVM .由于您的问题有点宽泛,我没有提供任何相同的源代码,而是提到了可以清楚地解决MVVM提到的问题的示例。

Clean Code Architecture can be followed which will clearly separate the responsibilities of each layer.可以遵循干净的代码架构,这将清楚地分离每一层的职责。

First of all application architecture needs to be restructured so that each layer has designated role in MVVM.首先需要重构应用程序架构,以便每一层在 MVVM 中都有指定的角色。 You can follow the following pattern for the same.您可以按照以下模式进行相同的操作。

  1. Only View Model will have access to UI layer只有 View Model 才能访问 UI 层
  2. View model will connect with Use Case layer查看 model 将与用例层连接
  3. Use case layer will connect with Data Layer用例层将与数据层连接
  4. No layer will have cyclic reference to other components.任何层都不会循环引用其他组件。
  5. So now for Database, Repository will decide, from which section the data needs to be fetched所以现在对于数据库,存储库将决定需要从哪个部分获取数据
  6. This can be either from Network or from DataBase.这可以来自网络或数据库。

All these points (except Database part) are covered over Medium Article , were each step is covered with actual API's.所有这些点(除了数据库部分)都包含在Medium Article中,每个步骤都包含实际的 API。 Along with that unit test is also covered.与该单元测试一起,也包括在内。

Libraries used are in this project are在这个项目中使用的库是

  1. Coroutines协程
  2. Retrofit Retrofit
  3. Koin (Dependency Injection) Can be replaced with dagger2 is required Koin (依赖注入) 可替换为 dagger2 是必需的
  4. MockWebServer (Testing) MockWebServer(测试)
  5. Language: Kotlin语言:Kotlin

Full Source code can be found over Github完整的源代码可以在Github上找到

Edit编辑

Kotlin is the official supported language for Android Development now. Kotlin 是 Android 开发的官方支持语言。 I suggest you should lean and migrate your java android projects to Kotlin.我建议您应该精简并将您的 java android 项目迁移到 Kotlin。

Still for converting Kotlin to Java, Go to Menu > Tools > Kotlin > Decompile Kotlin to Java Option Still for converting Kotlin to Java, Go to Menu > Tools > Kotlin > Decompile Kotlin to Java Option

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何将 MVVM 与 asyncTask 或任何其他东西一起使用? - How to Use MVVM With asyncTask or any other thing? 如何在Activity App Ui中使用Spring Boot Activity Rest api - How to use Spring Boot Activity Rest api in Activity App Ui 如何在Android中使用Asynctask类更改UI数据? - How can I change UI data using an Asynctask class in Android? 当活动中的asynctask完成时,如何重新加载片段中的ui? - When asynctask in activity finishes how to reload ui in fragment? 如果用户已经远离它,AsyncTask如何仍然使用Activity? - How can an AsyncTask still use an Activity if the user has already navigated away from it? 是否可以在 Swing 应用程序中使用 android UI 组件? - Is it possible to use android UI components in a Swing app? 不在AsyncTask的onPostExecute中设置Activity的实例变量或如何将数据从AsyncTask返回到主UI线程 - Instance variable of Activity not being set in onPostExecute of AsyncTask or how to return data from AsyncTask to main UI thread 如何将CSS用于Vaadin组件? - How can I use CSS for Vaadin components? 我怎样才能将OnPostExecute()的结果导入主活动,因为AsyncTask是一个单独的类? - How can i to get the result of OnPostExecute() to main activity because AsyncTask is a separate class? 如何更改特定类型的所有组件的UI? - How can I change the UI of all components of a certain type?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM