简体   繁体   English

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

[英]Network Awareness from Repository using RxJava

I am having trouble to find a proper solution to make view/viewmodel aware of network status, specially using RxJava. 我很难找到一个合适的解决方案,使view / viewmodel知道网络状态,特别是使用RxJava。

I tried to follow Google's NetworkBoundResource and Iammert's networkBoundResouce (also tried GithubBrowserSample but I just don't really understand the code or how it should be encapsulated. 我试图关注谷歌的NetworkBoundResourceIammert的networkBoundResouce (也试过GithubBrowserSample,但我真的不明白代码或它应该如何封装。

I'm looking for how can I implement NetworkBoundResource with RxJava2 in a way that the data gets refreshed automatically with the Database. 我正在寻找如何使用RxJava2实现NetworkBoundResource,使数据自动刷新数据库。

I will show what I currently have: 我将展示我目前拥有的东西:

A fragment with arch.lifecycle.ViewModel 与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("@");
    }
}

and in my UserRepository: 在我的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()));
            }
        }
    }

and my UserLocalDataSource: 和我的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"));
            }
        }));
    }
}

and the user remote data source: 和用户远程数据源:

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 interface ( It is using Room) 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);

}

What I want to know exactly is: How can I encapsulate my domain while still get access to possible problems like network and show it to the user using RxJava in the MVVM pattern? 我想要确切知道的是:我如何封装我的域,同时仍然可以访问网络等可能出现的问题,并使用MVVM模式中的RxJava向用户显示?

first of all let me copy past, the fetch from network method from the Github browser sample. 首先让我复制过去,从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)));
        }
    });
}

what I understand from this snippet that, you really don't monitor the network state, but the app just try to hit the Server, when there is a successful response it posted to the UI with a state of "Success", if it fails to get the Data from Server it trigger the onFetchFailed() method, and set the database data as the retrieved data with state of "fail". 我从这个片段中了解到,你真的没有监控网络状态,但是当应用程序成功响应它发布到具有“成功”状态的UI时,应用程序只是尝试命中服务器,如果失败从服务器获取数据它会触发onFetchFailed()方法,并将数据库数据设置为状态为“fail”的检索数据。

I think you need the state if you decided to show the cached data with a fail message of getting fresh ones. 如果您决定使用失败的消息显示缓存数据,我认为您需要状态。

there is many other abstract methods in the NetworkBoundResource class, so you can choose the right actions when you don't get a fresh data. NetworkBoundResource类中还有许多其他抽象方法,因此您可以在未获得新数据时选择正确的操作。

Speaking of Rxjava, I'm working on a fork from iammert repo to use Rxjava I'll share the link here once I finished it. 说到Rxjava,我正在使用iammert repo的fork来使用Rxjava我会在完成它之后在这里分享链接。

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM