通过MVVM在安卓平台上实现异常处理

6

我正在使用MVVM架构通过Retrofit在Android Studio中调用Web服务。我已经在我的View类中处理了服务的响应。但是,我面临的问题是如何处理异常并将其传递到我的View类。一种方法是在我的Bean类中创建构造函数,并将响应和错误都传递给它并更新UI。但我想要更优化的方式在UI内部处理异常。

这是我的仓库代码:

final MutableLiveData<MyBeanClass> myBeanClass = new MutableLiveData<>();
   ApiInterface apiInterface = ApiClient.getClientAuthentication().create(ApiInterface.class);
    Call<MyBeanClass> call = apiInterface.getData(id);
    call.enqueue(new Callback<MyBeanClass>() {
        @Override
        public void onResponse(Call<MyBeanClass> call, Response<MyBeanClass> response) {
            if(response.body()!=null) {
                myBeanClass.setValue(response.body());
            }
        }

        @Override
        public void onFailure(Call<MyBeanClass> call, Throwable t) {
         //How to handle exceptions here and pass the exception to UI without making constructor in bean class
        }
    });

    return myBeanClass;

另一种方法是使用另一个实时数据来检测错误,将您的错误设置为该数据,并在用户界面上观察它。 - Jeel Vankhede
你能通过一些代码来解释吗? - Ashmeet Arora
嗨,Ashmeet,你是如何处理它的?我也遇到了同样的问题,不确定如何处理不同的错误和异常。 - akash89
5个回答

13

这里是带错误处理的mvvm的完整实现代码。首先创建一个包含UI状态和资源的类。

public class Resource<T> {

    @NonNull public final Status status;
    @Nullable public final T data;
    @Nullable public final String message;

    private Resource(@NonNull Status status, @Nullable T data,
                     @Nullable String message) {
        this.status = status;
        this.data = data;
        this.message = message;
    }

    public static <T> Resource<T> success(@NonNull T data) {
        return new Resource<>(Status.SUCCESS, data, null);
    }

    public static <T> Resource<T> error(String msg, @Nullable T data) {
        return new Resource<>(Status.ERROR, data, msg);
    }

    public static <T> Resource<T> loading(@Nullable T data) {
        return new Resource<>(Status.LOADING, data, null);
    }


    public enum Status { SUCCESS, ERROR, LOADING }
}

在您的仓库类上执行以下操作:
public LiveData<Resource<MyBeanClass>> getDetail(String movieId) {
        final MutableLiveData<Resource<MyBeanClass>> myBeanClass = new MutableLiveData<>();
        ApiInterface apiInterface = new ApiClient().getClient().create(ApiInterface.class);

   Call<MyBeanClass> call = apiInterface.getData(id);
        call.enqueue(new Callback<MyBeanClass>() {

            @Override
            public void onResponse(Call<MyBeanClass> call, Response<MyBeanClass> response) {
                if (response.body() != null) {
                    MyBeanClass body = response.body();
                    myBeanClass.setValue(Resource.success(body));
                }
            }

            @Override
            public void onFailure(Call<MyBeanClass> call, Throwable t) {
                myBeanClass.setValue(Resource.error(t.getMessage(),null));
            }
        });

        return myBeanClass;
    }

在你的viewModel类中:
public class MyViewModel extends ViewModel {

    private LiveData<Resource<MyBeanClass>> myLiveData;

    public void init(String id) {
        movieLiveData = new MyRepository().getInstance().getDetail(id);

    }

    
    public LiveData<Resource<MyBeanClass>> getMyLiveData() {
        return myLiveData;
    }
}

在您的Activity类上

final MyViewModel viewModel =
                ViewModelProviders.of(this).get(MyViewModel.class);
        viewModel.init(id);
        viewModel.getMyLiveData().observe(this, finalData -> {

            switch (finalData.status) {
                case SUCCESS:
                    loadDetail(finalData.data);
                    break;
                case LOADING:
                    break;
                case ERROR:
                    Toast.makeText(this, "no Internet", Toast.LENGTH_SHORT).show();
                    break;
            }

        });

如果有帮助的话,请别忘了给个赞。祝编码愉快。

8

不需要创建两个可变类。你只需为错误和成功状态甚至是加载状态创建一个包装对象。

data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
    companion object {
        fun <T> success(data: T?): Resource<T> {
            return Resource(SUCCESS, data, null)
        }

        fun <T> error(msg: String, data: T?): Resource<T> {
            return Resource(ERROR, data, msg)
        }

        fun <T> loading(data: T?): Resource<T> {
            return Resource(LOADING, data, null)
        }
    }
}

然后使用MutableLiveData作为这种类型

final MutableLiveData<Resource<MyBeanClass>> myBeanClass = new MutableLiveData<>();
           ApiInterface apiInterface = ApiClient.getClientAuthentication().create(ApiInterface.class);
            Call<MyBeanClass> call = apiInterface.getData(id);
            call.enqueue(new Callback<MyBeanClass>() {
                @Override
                public void onResponse(Call<MyBeanClass> call, Response<MyBeanClass> response) {
                    if(response.body()!=null) {
                    myBeanClass.setValue(Resource<MyBeanClass>.success(response.body));
                    }
                }

                @Override
                public void onFailure(Call<MyBeanClass> call, Throwable t) {
     myBeanClass.setValue(Resource<MyBeanClass>.error(t.getLocalizedMessage()));
                }
            });

            return myBeanClass;

你可以查看这个Google示例:https://github.com/googlesamples/android-architecture-components/tree/master/GithubBrowserSample。该示例与Android架构组件相关。

是的,我完成了这个..现在我的问题是如何在 UI 中处理异常..? - Ashmeet Arora
在您的对象中,将一个整数作为属性来指示资源对象(Mybeanclass)的状态。 例如,如果资源是成功的,它将是1; 如果资源失败,则为0。 在这种情况下,您可以检查对象整数值以及其是否为1或0,并进行处理。 - Kaung Khant Thu
但我的观点是如何从存储库获取特定异常的名称到用户界面? - Ashmeet Arora
使用包装类时无法获取Retrofit数据,它会破坏数据响应的结构。 - Nguyen Hoà

1

您可以在这里进行接口。从onFailure调用接口方法并提供UI侧的实现,以便出现错误时,您将进入UI侧。


1
public MutableLiveData<String> responseMessage = new MutableLiveData<>();
public MutableLiveData<String> errorMessage = new MutableLiveData<>();                                           ApiInterface apiInterface = ApiClient.getClientAuthentication().create(ApiInterface.class);
Call<MyBeanClass> call = apiInterface.getData(id);
call.enqueue(new Callback<MyBeanClass>() {
    @Override
    public void onResponse(Call<MyBeanClass> call, Response<MyBeanClass> response) {
        if(response.body()==null) {
          //pick server error message
          JSONObject jObjError = new JSONObject(response.errorBody().string());
          Log.d("Error",jObjError.getString("message"))
        }else{
        myBeanClass.setValue(Resource<MyBeanClass>.success(response.body));
        }
    }

    @Override
    public void onFailure(Call<MyBeanClass> call, Throwable t) {
     Log.d("Error", t.getMessage());
     errorMessage.setValue(t.getMessage());
    }
});

只需使用两个MutableLiveData,一个用于成功响应,另一个用于错误响应,并根据这些响应更新您的UI。


0

查看如何从您的代码库中完成它:

//Take it globally in your repository class, and provide getter for it.
final MutableLiveData<MyBeanClass> myBeanClass = new MutableLiveData<>();
final MutableLiveData<Throwable> error = new MutableLiveData<>();

public void someApiCallMethod() {
    // In your method call for API
    ApiInterface apiInterface = 
    ApiClient.getClientAuthentication().create(ApiInterface.class);
    Call<MyBeanClass> call = apiInterface.getData(id);
    call.enqueue(new Callback<MyBeanClass>() {
        @Override
        public void onResponse(Call<MyBeanClass> call, Response<MyBeanClass> response) {
            if(response.body()!=null) {
                myBeanClass.setValue(response.body());
            }
            // Even you can handle your response error if it's in your response.
        }

        @Override
        public void onFailure(Call<MyBeanClass> call, Throwable t) {
            //Set your error live data from here
            error.setValue(t);
        }
    });
}

从您的ViewModel类中创建一个方法,调用您的repo API方法,另一个方法提供LiveData以在UI上进行观察。
希望能有所帮助!

是的,我理解了仓库部分,但是在视图模型方面我遇到了问题。您能否告诉我如何编写视图模型代码以获取两种不同类型的数据,即错误和响应? - Ashmeet Arora
实际上答案很早就有了,最近我了解到我们可以从这里包装两件事情:https://developer.android.com/jetpack/docs/guide#addendum。如果满足您的情况,请告诉我,我将相应地编辑我的答案。 - Jeel Vankhede
我无法从这个链接中获得解决方案。我想通过视图模型将我的存储库类中出现的特定异常名称传递到我的视图,并且我已经通过视图模型从我的存储库类中获取了响应,但是在出现任何异常的情况下该如何处理?如何将该异常传递到我的视图中... - Ashmeet Arora

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接