Знание сети из репозитория с использованием RxJava

У меня возникли проблемы с поиском правильного решения, чтобы сделать view/viewmodel информацией о состоянии сети, особенно используя RxJava.

Я попытался следовать Google NetworkBoundResource и Iammert networkBoundResouce (также попробовал GithubBrowserSample но я просто не понимаю код или как его инкапсулировать.

Я ищу, как я могу реализовать NetworkBoundResource с RxJava2 таким образом, чтобы данные автоматически обновлялись с помощью базы данных.

Я покажу, что у меня есть:

Фрагмент с 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 (используется Room)

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

}

То, что я хочу точно знать, это: Как я могу инкапсулировать свой домен, все еще получая доступ к возможным проблемам, таким как сеть, и показывать его пользователю, используя RxJava в шаблоне MVVM?

Ответ 1

прежде всего позвольте мне скопировать прошлое, выбор из сетевого метода из примера браузера 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)));
        }
    });
}

что я понимаю из этого фрагмента, что вы действительно не контролируете состояние сети, но приложение просто пытается попасть на сервер, когда есть успешный ответ, который он отправил в пользовательский интерфейс с состоянием "Успех", если ему не удается получить данные с сервера, он запускает метод onFetchFailed() и устанавливает данные базы данных как извлеченные данные с состоянием "fail".

Я думаю, вам нужно состояние, если вы решили показать кэшированные данные с сообщением об ошибке получения свежих.

существует много других абстрактных методов в классе NetworkBoundResource, поэтому вы можете выбрать правильные действия, когда не получите свежие данные.

Говоря о Rxjava, я работаю над вилкой из iammert repo, чтобы использовать Rxjava. Я поделюсь ссылкой здесь, когда закончил.