Mais conteúdo relacionado Semelhante a Petcube epic battle: architecture vs product. UA Mobile 2017. (20) Petcube epic battle: architecture vs product. UA Mobile 2017.10. Petcube Android app 2.0+
✦ Rapidly changing team squad
✦ 3+ years of active development
✦ Startup spirit
21. Choosing a pattern
✦ Scalable, modular and testable (sic!)
✦ Dependency Injection
✦ Reactive Programming
22. Choosing a pattern
✦ Scalable, modular and testable (sic!)
✦ Dependency Injection
✦ Reactive Programming
✦ Control the spreading
49. Pros
✦ Code reuse
✦ Complex flow without callback hell
✦ Presentation separated from business
50. Pros
✦ Code reuse
✦ Complex flow without callback hell
✦ Presentation separated from business
✦ Evolution
55. UseCase.java
public abstract class UseCase<T> {
private final Scheduler mThreadExecutor;
private final Scheduler mPostExecutionThread;
private Subscription mSubscription = Subscriptions.empty();
public UseCase() {
mThreadExecutor = Schedulers.io();
mPostExecutionThread = AndroidSchedulers.mainThread();
}
protected UseCase(@NonNull Scheduler threadExecutor,
@NonNull Scheduler postExecutionThread) { … }
public void execute(Subscriber<T> useCaseSubscriber) {
mSubscription = setupObservable()
.subscribe(useCaseSubscriber);
}
…
}
56. UseCase.java
…
public void unsubscribe() {
if (!mSubscription.isUnsubscribed()) {
mSubscription.unsubscribe();
}
}
protected Observable<T> setupObservable() {
return buildUseCaseObservable()
.subscribeOn(mThreadExecutor)
.observeOn(mPostExecutionThread);
}
protected abstract Observable<T> buildUseCaseObservable();
}
58. GetCurrentUserUseCase.java
class GetCurrentUserUseCase extends UseCase<Profile> {
private final LoginRepository mRepository;
public GetCurrentUserUseCase(@NonNull LoginRepository repository) {
mRepository = repository;
}
@Override
protected Observable<Profile> buildUseCaseObservable() {
return mRepository.getCurrentUser();
}
}
60. GetUserUseCase.java
class GetUserUseCase extends UseCase<User> {
private final UserRepository mRepository;
private long mUserId;
GetUserUseCase(@NonNull UserRepository repository) {
mRepository = repository;
}
void setUserId(@IntRange(from = 1) long userId) {
mUserId = userId;
}
@Override
protected Observable<User> buildUseCaseObservable() {
try {
if (mUserId < 1L) {
throw new IllegalArgumentException("invalid user id: "
+ mUserId);
}
return mRepository.getUser(mUserId);
} finally {
mUserId = 0L;
}
}
}
61. FriendsPresenter.java
class FriendsPresenter extends BasePresenter<FriendsContract.View>
implements FriendsContract.Presenter {
…
private final Map<Long, Subscriber<User>> mIdSubscribersMap = new HashMap<>();
@Inject
public FriendsPresenter(@NonNull GetUserUseCase getUserUseCase,
@NonNull ErrorHandler errorHandler) {
/* bind params to fields */ }
@Override
public void loadFriend(long userId) {
Subscriber<User> friendSubscriber = mIdSubscribersMap.get(userId);
if (friendSubscriber == null) {
friendSubscriber = new UserSubscriber(userId);
mIdSubscribersMap.put(userId, friendSubscriber);
}
mGetUserUseCase.setUserId(userId);
mGetUserUseCase.execute(friendSubscriber);
}
@Override
public void destroy() {
for (Subscriber<User> subscriber : mIdSubscribersMap.values()) {
subscriber.unsubscribe();
}
mIdSubscribersMap.clear();
super.destroy();
}
…
62. FriendsPresenter.java
…
private class UserSubscriber extends Subscriber<User> {
private final long mUserId;
private UserSubscriber(long userId) {
mUserId = userId;
}
@Override
public void onCompleted() { /*NOP*/ }
@Override
public void onError(Throwable e) {
Log.e(TAG, "User loading failed, id =" + mUserId, e);
e = mErrorHandler.handleException(e);
getView().showError(e.getMessage());
}
@Override
public void onNext(User user) {
getView().displayFriend(user);
}
}
64. Friend.java
class Friend {
private final User mUser;
private final List<Petcube> mPetcube;
public Friend(@NonNull User user, @Nullable List<Petcube> petcube) {
mUser = user;
mPetcube = petcube;
}
}
65. GetFriendUseCase.java
class GetFriendUseCase extends UseCase<Friend> {
private final UserRepository mUserRepository;
private final PetcubeRepository mPetcubeRepository;
private long mUserId;
GetFriendUseCase(@NonNull UserRepository userRepository,
@NonNull PetcubeRepository petcubeRepository) {
mUserRepository = userRepository;
mPetcubeRepository = petcubeRepository;
}
void setUserId(@IntRange(from = 1) long userId) {
mUserId = userId;
}
…
68. GetFriendsStatusesUseCase.java
class GetFriendsStatusesUseCase extends UseCase<UserStatus> {
…
private final UserRepository mUserRepository;
private final PublishSubject<UserStatus> mStatusPublishSubject
= PublishSubject.create();
private final Map<Long, Subscription> mSubscriptionsIdMap = new HashMap<>();
…
@Override
protected Observable<UserStatus> buildUseCaseObservable() {
return mStatusPublishSubject.asObservable();
}
…
69. GetFriendsStatusesUseCase.java
…
void addUserId(final long userId) {
if (mSubscriptionsIdMap.get(userId) == null) {
final Subscription subscription =
Observable.interval(POLLING_DELAY, TimeUnit.SECONDS)
.flatMap(aLong -> mUserRepository.getUserStatus(userId))
.filter(userStatus ->
mSubscriptionsIdMap.containsKey(userStatus.getId()))
.retry()
.distinct()
.timeout(POLLING_TIMEOUT, TimeUnit.SECONDS)
.subscribe(mStatusPublishSubject::onNext, throwable ->
Log.e(TAG, "user status load failed, id = " + userId, throwable));
mSubscriptionsIdMap.put(userId, subscription);
}
}
70. GetFriendsStatusesUseCase.java
…
void removeUserId(long userId) {
Subscription subscription = mSubscriptionsIdMap.remove(userId);
if (subscription != null) {
subscription.unsubscribe();
}
}
@Override
public void unsubscribe() {
for (Subscription subscription : mSubscriptionsIdMap.values()) {
subscription.unsubscribe();
}
mSubscriptionsIdMap.clear();
super.unsubscribe();
}