使用RxJava从存储库中获得网络意识

时间:2018-01-09 13:37:57

标签: android rx-java2 rx-android

我无法找到合适的解决方案来让view / viewmodel了解网络状态,特别是使用RxJava。

我尝试关注Google's NetworkBoundResourceIammert's networkBoundResouce(也试过GithubBrowserSample 但我真的不明白代码或它应该如何封装。

我正在寻找如何使用RxJava2实现NetworkBoundResource,以便数据通过数据库自动刷新。

我将展示我目前拥有的东西:

具有arch.lifecycle.ViewModel

的片段
public class LoginViewModel extends ViewModel {

    static final int SCREEN_JUST_BUTTONS = 0;
    static final int SCREEN_LOGIN_INPUT = 1;
    static final int SCREEN_PASS_INPUT = 2;

    // using a PublishSubject because we are not interested in the last object that was emitted
    // before subscribing. Like this we avoid displaying the snackbar multiple times
    @NonNull
    private final PublishSubject<Integer> snackbarText;

    private int SCREEN = 0;

    private LoginUiModel loginUiModel;
    @NonNull
    private BaseSchedulerProvider schedulerProvider;
    @NonNull
    private UserRepository userRepository;

    @Inject
    LoginViewModel(@NonNull final BaseSchedulerProvider schedulerProvider, @NonNull final UserRepository userRepository) {
        this.schedulerProvider = schedulerProvider;
        this.userRepository = userRepository;
        snackbarText = PublishSubject.create();
        disposables = new CompositeDisposable();
        loginUiModel = new LoginUiModel();
    }

    int getScreen() {
        return SCREEN;
    }

    void setScreen(final int screen) {
        this.SCREEN = screen;
    }

    @NonNull
    Observable<Integer> getSnackbarText() {
        return snackbarText;
    }

    LoginUiModel getLoginUiModel() {
        return loginUiModel;
    }


    public Single<User> login(final String login, final String password) {
        return userRepository.login(login, password)
                             .subscribeOn(schedulerProvider.io())
                             .observeOn(schedulerProvider.ui())
                             .doOnError(this::handleErrorLogin);
    }

    private void handleErrorLogin(final Throwable t) {
        if (t instanceof HttpException) {
            showSnackbar(R.string.Login_invalid_input);
        } else if (t instanceof IOException) {
            showSnackbar(R.string.Common_no_connection);
        } else {
            showSnackbar(R.string.Common_error_loading);
        }
    }

    private void showSnackbar(@StringRes int textId) {
        snackbarText.onNext(textId);
    }

    void forgotPassword() {
        if (isValidEmail(loginUiModel.getEmail())) {
            showSnackbar(R.string.Login_password_sent);
        } else {
            showSnackbar(R.string.Login_invalid_login);
        }
        //TODO;
    }

    boolean isValidEmail(@Nullable final String text) {
        return text != null && text.trim().length() > 3 && text.contains("@");
    }
}

并在我的UserRepository中:

@Singleton
public class UserRepository {

    @NonNull
    private final UserDataSource userRemoteDataSource;

    @NonNull
    private final UserDataSource userLocalDataSource;

    @NonNull
    private final RxSharedPreferences preferences;

    @Inject
    UserRepository(@NonNull RxSharedPreferences rxSharedPreferences, @NonNull @Remote UserDataSource userRemoteDataSource, @NonNull @Local UserDataSource
            userLocalDataSource) {
        this.userRemoteDataSource = userRemoteDataSource;
        this.userLocalDataSource = userLocalDataSource;
        preferences = rxSharedPreferences;
    }

    @NonNull
    public Single<User> login(final String email, final String password) {
        return userRemoteDataSource
                .login(email, password) // busca do webservice
                .flatMap(userLocalDataSource::saveUser) // salva no banco local
                .flatMap(user -> userLocalDataSource.login(user.getEmail(), user.getPassword())) // busca usuário no banco local como SSOT
                .doOnSuccess(this::saveUserId); // salva o ID do usuário nas preferências.
    }

    public Single<User> saveUser(String email, String password, String name, String phone, String company) {
        User user = new User(0, name, email, password, phone, company);

        return saveUser(user);
    }

    private Single<User> saveUser(final User user) {
        return userRemoteDataSource.saveUser(user)
                                   .flatMap(userLocalDataSource::saveUser)
                                   .doOnSuccess(this::saveUserId); // salva o ID do usuário nas preferências.
    }

    private void saveUserId(final User user) {
        preferences.getLong(Consts.KEY_USER_ID__long).set(user.getUserId());
    }

    public Flowable<User> getUserUsingSystemPreferences() {
        Preference<Long> userIdPref = preferences.getLong(Consts.KEY_USER_ID__long, -1L);
        if (userIdPref.get() <= 0) {
            //nao deveria acontecer, mas se acontecer, reseta:
            userIdPref.delete();
            return Flowable.error(new RuntimeException("Not logged in."));
        } else if (!userIdPref.isSet()) {
            return Flowable.error(new RuntimeException("Not logged in."));
        } else {
            return userLocalDataSource.getUser(String.valueOf(userIdPref.get()));
            }
        }
    }

和我的UserLocalDataSource:

@Singleton
public class UserLocalDataSource implements UserDataSource {

    private final UserDao userDao;

    @Inject
    UserLocalDataSource(UserDao userDao) {
        this.userDao = get(userDao);
    }

    @NonNull
    @Override
    public Single<User> login(final String email, final String password) {
        return userDao.login(email, password);
    }

    @Override
    public Flowable<User> getUser(final String id) {
        return userDao.getUserById(Long.valueOf(id));
    }

    @Override
    public Single<User> saveUser(final User user) {
        return Single.create(e -> {
            long id = userDao.insert(user);
            user.setUserId(id);
            e.onSuccess(user);
        });
    }

    @Override
    public Completable deleteUser(final String id) {
        return getUser(id).flatMapCompletable(user -> Completable.create(e -> {
            int numberOfDeletedRows = userDao.delete(user);
            if (numberOfDeletedRows > 0) {
                e.onComplete();
            } else {
                e.onError(new Exception("Tried to delete a non existing user"));
            }
        }));
    }
}

和用户远程数据源:

public class UserRemoteDataSource implements UserDataSource {

    private final ISApiService apiService;

    @Inject
    UserRemoteDataSource(ISApiService apiService) {
        this.apiService = get(apiService);
    }

    @NonNull
    @Override
    public Single<User> login(final String email, final String password) {
        return apiService.login(email, password);
    }

    @Override
    public Flowable<User> getUser(final String id) {
        throw new RuntimeException("NO getUser endpoint");
    }

    @Override
    public Single<User> saveUser(final User user) {
        Map<String, String> params = new HashMap<>();

        params.put(ISApiConsts.EMAIL, user.getEmail());
        params.put(ISApiConsts.NAME, user.getName());
        params.put(ISApiConsts.PASSWORD, user.getPassword());
        params.put(ISApiConsts.PHONE, user.getPhone());
        params.put(ISApiConsts.COMPANY, user.getCompany());

        return apiService.signUp(params);
    }

    @Override
    public Completable deleteUser(final String id) {
        //can't delete user.
        return Completable.complete();
    }
}

UserDao界面(正在使用房间)

@Dao
public interface UserDao extends BaseDao<User> {

    @Query("SELECT * FROM user WHERE user_id = :id")
    Flowable<User> getUserById(long id);

    @Query("SELECT * FROM user WHERE email = :login AND password = :password")
    Single<User> login(String login, String password);

}

我想知道的是:我如何封装我的域,同时仍然可以访问网络等可能出现的问题,并使用MVVM模式中的RxJava向用户显示它?

1 个答案:

答案 0 :(得分:0)

首先让我复制过去,从Github浏览器样本中获取网络方法。

private void fetchFromNetwork(final LiveData<ResultType> dbSource) {
    LiveData<ApiResponse<RequestType>> apiResponse = createCall();
    // we re-attach dbSource as a new source, it will dispatch its latest value quickly
    result.addSource(dbSource, newData -> setValue(Resource.loading(newData)));
    result.addSource(apiResponse, response -> {
        result.removeSource(apiResponse);
        result.removeSource(dbSource);
        //noinspection ConstantConditions
        if (response.isSuccessful()) {
            appExecutors.diskIO().execute(() -> {
                saveCallResult(processResponse(response));
                appExecutors.mainThread().execute(() ->
                        // we specially request a new live data,
                        // otherwise we will get immediately last cached value,
                        // which may not be updated with latest results received from network.
                        result.addSource(loadFromDb(),
                                newData -> setValue(Resource.success(newData)))
                );
            });
        } else {
            onFetchFailed();
            result.addSource(dbSource,
                    newData -> setValue(Resource.error(response.errorMessage, newData)));
        }
    });
}

我从这个片段中了解到,你真的不监视网络状态,但应用程序只是尝试点击服务器,当它成功响应它发布到状态为“成功”的UI时,如果它无法从服务器获取数据,则会触发onFetchFailed()方法,并将数据库数据设置为状态为“失败”的检索数据。

如果您决定使用失败的消息显示缓存数据,我认为您需要状态。

NetworkBoundResource类中还有许多其他抽象方法,因此您可以在没有获得新数据时选择正确的操作。

说到Rxjava,我正在使用iammert repo的fork来使用Rxjava我会在完成它之后在这里分享链接。