/* * Copyright 2016 Jacek Marchwicki <jacek.marchwicki@gmail.com> * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.appunite.rx.example.dao.posts; import com.appunite.cache.Cache; import com.appunite.login.CurrentLoggedInUserDao; import com.appunite.rx.ResponseOrError; import com.appunite.rx.example.dao.internal.helpers.CacheProvider; import com.appunite.rx.example.dao.internal.helpers.RequestHelper; import com.appunite.rx.example.dao.posts.model.AddPost; import com.appunite.rx.example.dao.posts.model.Post; import com.appunite.rx.example.dao.posts.model.PostWithBody; import com.appunite.rx.example.dao.posts.model.PostsIdsResponse; import com.appunite.rx.example.dao.posts.model.PostsResponse; import com.appunite.rx.operators.MoreOperators; import com.appunite.rx.operators.OperatorMergeNextToken; import com.appunite.rx.subjects.CacheSubject; import java.util.ArrayList; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.inject.Singleton; import rx.Observable; import rx.Observer; import rx.Scheduler; import rx.functions.Action1; import rx.functions.Func1; import rx.subjects.PublishSubject; @Singleton public class PostsDao { @Nonnull private final Observable<ResponseOrError<PostsResponse>> posts; @Nonnull private final Observable<ResponseOrError<PostsIdsResponse>> postsIds; @Nonnull private final PublishSubject<Object> refreshSubject = PublishSubject.create(); @Nonnull private final Cache<String, PostDao> cache; @Nonnull private final Scheduler networkScheduler; @Nonnull private final PostsService postsService; @Nonnull private final PublishSubject<Object> loadMoreSubject = PublishSubject.create(); @Nonnull private final CurrentLoggedInUserDao currentLoggedInUserDao; public PostsDao(@Nonnull final Scheduler networkScheduler, @Nonnull final PostsService postsService, @Nonnull final CacheProvider cacheProvider, @Nonnull final CurrentLoggedInUserDao currentLoggedInUserDao) { this.networkScheduler = networkScheduler; this.postsService = postsService; this.currentLoggedInUserDao = currentLoggedInUserDao; posts = currentLoggedInUserDao .currentLoggedInUserObservable() .compose(ResponseOrError.switchMap(new Func1<CurrentLoggedInUserDao.LoggedInUserDao, Observable<ResponseOrError<PostsResponse>>>() { @Override public Observable<ResponseOrError<PostsResponse>> call(@Nonnull CurrentLoggedInUserDao.LoggedInUserDao loggedInUserDao) { return loadMoreSubject.startWith((Object) null) .lift(loadMorePosts(networkScheduler, postsService, loggedInUserDao)) .compose(CacheSubject.behaviorRefCount(cacheProvider.<PostsResponse>getCacheCreatorForKey("user: " + loggedInUserDao.userId() +", posts", PostsResponse.class))) .compose(ResponseOrError.<PostsResponse>toResponseOrErrorObservable()) .compose(MoreOperators.<PostsResponse>repeatOnError(networkScheduler)) .subscribeOn(networkScheduler) .unsubscribeOn(networkScheduler) .compose(MoreOperators.<ResponseOrError<PostsResponse>>refresh(refreshSubject)) .compose(MoreOperators.<ResponseOrError<PostsResponse>>cacheWithTimeout(networkScheduler)); } })); postsIds = currentLoggedInUserDao .currentLoggedInUserObservable() .compose(ResponseOrError.switchMap(new Func1<CurrentLoggedInUserDao.LoggedInUserDao, Observable<ResponseOrError<PostsIdsResponse>>>() { @Override public Observable<ResponseOrError<PostsIdsResponse>> call(CurrentLoggedInUserDao.LoggedInUserDao loggedInUserDao) { return loadMoreSubject.startWith((Object) null) .lift(loadMorePostsIds(networkScheduler, postsService, loggedInUserDao)) .compose(ResponseOrError.<PostsIdsResponse>toResponseOrErrorObservable()) .compose(MoreOperators.<PostsIdsResponse>repeatOnError(networkScheduler)) .subscribeOn(networkScheduler) .unsubscribeOn(networkScheduler) .compose(MoreOperators.<ResponseOrError<PostsIdsResponse>>refresh(refreshSubject)) .compose(MoreOperators.<ResponseOrError<PostsIdsResponse>>cacheWithTimeout(networkScheduler)); } })); cache = new Cache<>(new Cache.CacheProvider<String, PostDao>() { @Nonnull @Override public PostDao load(@Nonnull String id) { return new PostDao(id); } }); } @Nonnull private OperatorMergeNextToken<PostsIdsResponse, Object> loadMorePostsIds(@Nonnull final Scheduler networkScheduler, @Nonnull final PostsService postsService, @Nonnull final CurrentLoggedInUserDao.LoggedInUserDao loggedInUserDao) { return OperatorMergeNextToken .create(new Func1<PostsIdsResponse, Observable<PostsIdsResponse>>() { @Override public Observable<PostsIdsResponse> call(@Nullable final PostsIdsResponse previous) { if (previous == null) { return createRequest(null); } else { final String nextToken = previous.nextToken(); if (nextToken == null) { return Observable.never(); } return createRequest(nextToken) .map(joinWithPreviousResponse(previous)); } } @Nonnull private Func1<PostsIdsResponse, PostsIdsResponse> joinWithPreviousResponse(@Nonnull final PostsIdsResponse previous) { return new Func1<PostsIdsResponse, PostsIdsResponse>() { @Override public PostsIdsResponse call(PostsIdsResponse moreData) { return new PostsIdsResponse( moreData.title(), concatTwoLists(previous.items(), moreData.items()), moreData.nextToken()); } }; } @Nonnull private Observable<PostsIdsResponse> createRequest(@Nullable final String nextToken) { return RequestHelper.request(loggedInUserDao, networkScheduler, new Func1<String, Observable<PostsIdsResponse>>() { @Override public Observable<PostsIdsResponse> call(String authorization) { return postsService.listPostsIds(authorization, nextToken); } }); } }); } @Nonnull private OperatorMergeNextToken<PostsResponse, Object> loadMorePosts(@Nonnull final Scheduler networkScheduler, @Nonnull final PostsService postsService, @Nonnull final CurrentLoggedInUserDao.LoggedInUserDao loggedInUserDao) { return OperatorMergeNextToken .create(new Func1<PostsResponse, Observable<PostsResponse>>() { @Override public Observable<PostsResponse> call(@Nullable final PostsResponse previous) { if (previous == null) { return createRequest(null); } else { final String nextToken = previous.nextToken(); if (nextToken == null) { return Observable.never(); } return createRequest(nextToken) .map(joinWithPreviousResponse(previous)); } } @Nonnull private Func1<PostsResponse, PostsResponse> joinWithPreviousResponse(@Nonnull final PostsResponse previous) { return new Func1<PostsResponse, PostsResponse>() { @Override public PostsResponse call(PostsResponse moreData) { return new PostsResponse( moreData.title(), concatTwoLists(previous.items(), moreData.items()), moreData.nextToken()); } }; } @Nonnull private Observable<PostsResponse> createRequest(@Nullable final String nextToken) { return RequestHelper.request(loggedInUserDao, networkScheduler, new Func1<String, Observable<PostsResponse>>() { @Override public Observable<PostsResponse> call(String authorization) { return postsService.listPosts(authorization, nextToken); } }); } }); } @Nonnull public PostDao postDao(@Nonnull final String id) { return cache.get(id); } @Nonnull public Observer<Object> loadMoreObserver() { return loadMoreSubject; } @Nonnull public Observable<ResponseOrError<PostsResponse>> postsObservable() { return posts; } @Nonnull public Observable<ResponseOrError<PostsIdsResponse>> postsIdsObservable() { return postsIds; } @Nonnull public Observer<Object> refreshObserver() { return refreshSubject; } @Nonnull public Observable<ResponseOrError<PostWithBody>> postRequestObserver(@Nonnull final AddPost post) { return currentLoggedInUserDao.currentLoggedInUserObservable() .first() .compose(ResponseOrError.flatMap(new Func1<CurrentLoggedInUserDao.LoggedInUserDao, Observable<ResponseOrError<PostWithBody>>>() { @Override public Observable<ResponseOrError<PostWithBody>> call(CurrentLoggedInUserDao.LoggedInUserDao loggedInUserDao) { return postRequestObserver(loggedInUserDao, post); } })); } @Nonnull private Observable<ResponseOrError<PostWithBody>> postRequestObserver(@Nonnull CurrentLoggedInUserDao.LoggedInUserDao loggedInUserDao, @Nonnull final AddPost post) { return RequestHelper.request(loggedInUserDao, networkScheduler, new Func1<String, Observable<PostWithBody>>() { @Override public Observable<PostWithBody> call(String authorization) { return postsService.createPost(authorization, post); } }) .doOnNext(new Action1<PostWithBody>() { @Override public void call(PostWithBody postWithBody) { refreshSubject.doOnNext(null); } }) .compose(ResponseOrError.<PostWithBody>toResponseOrErrorObservable()); } public class PostDao { @Nonnull private final PublishSubject<Object> refreshSubject = PublishSubject.create(); @Nonnull private final Observable<ResponseOrError<PostWithBody>> postWithBodyObservable; PostDao(@Nonnull final String id) { postWithBodyObservable = currentLoggedInUserDao.currentLoggedInUserObservable() .compose(ResponseOrError.switchMap(new Func1<CurrentLoggedInUserDao.LoggedInUserDao, Observable<ResponseOrError<PostWithBody>>>() { @Override public Observable<ResponseOrError<PostWithBody>> call(CurrentLoggedInUserDao.LoggedInUserDao loggedInUserDao) { return RequestHelper.request(loggedInUserDao, networkScheduler, new Func1<String, Observable<PostWithBody>>() { @Override public Observable<PostWithBody> call(String authorization) { return postsService.getPost(authorization, id); } }) .compose(ResponseOrError.<PostWithBody>toResponseOrErrorObservable()) .compose(MoreOperators.<PostWithBody>repeatOnError(networkScheduler)) .compose(MoreOperators.<ResponseOrError<PostWithBody>>refresh(refreshSubject)) .compose(MoreOperators.<ResponseOrError<PostWithBody>>cacheWithTimeout(networkScheduler)); } })); } @Nonnull public Observable<ResponseOrError<PostWithBody>> postWithBodyObservable() { return postWithBodyObservable; } @Nonnull public Observable<ResponseOrError<Post>> postObservable() { return postWithBodyObservable .compose(ResponseOrError.map(new Func1<PostWithBody, Post>() { @Override public Post call(PostWithBody o) { return o; } })); } @Nonnull public Observer<Object> refreshObserver() { return refreshSubject; } } @Nonnull private static <T> List<T> concatTwoLists(@Nonnull List<T> firstList, @Nonnull List<T> secondList) { final ArrayList<T> posts = new ArrayList<>(firstList.size() + secondList.size()); posts.addAll(firstList); posts.addAll(secondList); return posts; } }