본 포스팅은 DroidKaigi 2017 ~ 大規模アプリのリノベーション 을 기본으로 번역하여 작성했습니다
제 일본어 실력으로 인하여 오역이나 오타가 발생할 수 있습니다.
실제 슬라이드의 일본어
부분을 번역했다는 점 양해바랍니다.
Ryo Kitramura
## 3p, Hatena
(교토 오피스)
대규모!
2011 - Android 2.3 ~ 2016 - Android 7.1
오래된 코드가 계속 남아있다.
리팩터링하고 싶다
리노베이션의 흐름
도메인 지식의 획득
화면을 알자
스크린샷을 마구 찍는다
디자인 의도 확인
설계
라이브러리 선정
아키텍쳐 결정
샘플 앱 만들기
완성된 설계
View - ViewModel - Interactor - Repository
Repository
public interface EntryRepository {
Single<List<Entry>> getGalleryEntries(Category category);
}
public class EntryRepositoryImpl implements EntryRepository {
BookmarkAPI mBookmarkAPI;
@Inject
public EntryRepositoryImpl(BookmarkAPI bookmarkAPI) {
mBookmarkAPI = bookmarkAPI;
}
@Override
public Single<List<Entry>> getGalleryEntries(Category category) {
return mBookmarkAPI
.getEntryListGallery(category.getId())
.map(entries -> Lists.transform(entries, e ->
new Entry(e, true)));
}
}
@Module
public abstract class EntryModule {
@Singleton
@Binds
public abstract EntryRepository
provideEntryRepository(EntryRepositoryImpl impl);
}
Interactor
public class GetUserPage {
private final UserRepository mUserRepository;
private final SchedulerSwitch mSchedulerSwitch;
@Inject
public GetUserPage(UserRepository repository, SchedulerSwitch schedulerSwitch) {
mUserRepository = repository;
mSchedulerSwitch = schedulerSwitch;
}
public Single<UserPage> execute(HatenaId hatenaId, int limit) {
return mUserRepository
.getUserPage(hatenaId, limit)
.compose(mSchedulerSwitch.fromIOtoUI());
}
}
public class SchedulerSwitch {
private final Scheduler mIOScheduler;
private final Scheduler mUIScheduler;
@Inject
public SchedulerSwitch(@IOScheduler Scheduler ioScheduler, @UIScheduler Scheduler uiScheduler) {
mIOScheduler = ioScheduler;
mUIScheduler = uiScheduler;
}
public <T> SchedulerSwitchTransformer<T> fromIOtoUI() {
return new SchedulerSwitchTransformer<>(mIOScheduler, mUIScheduler);
}
}
public class SchedulerSwitchTransformer<T> implements
ObservableTransformer<T, T>,
FlowableTransformer<T, T>,
SingleTransformer<T, T>,
MaybeTransformer<T, T>,
CompletableTransformer
{
…
public SchedulerSwitchTransformer(Scheduler from, Scheduler to) {
mFromScheduler = from;
mToScheduler = to;
}
@Override
public ObservableSource<T> apply(Observable<T> upstream) {
return upstream.subscribeOn(mFromScheduler).observeOn(mToScheduler);
}
…
ViewModel
public class CommentsViewModel {
…
private final BehaviorSubject<Page<BookmarkCommentViewModel>> mPages = BehaviorSubject.create();
public final Observable<Page<BookmarkCommentViewModel>> pages = mPages.hide();
private final PublishSubject<Throwable> mErrors = PublishSubject.create();
public final Observable<Throwable> errors = mErrors.hide();
@Inject
public CommentsViewModel(Observable<FragmentEvent> lifecycle, GetEntryCommentList getEntryCommentList, AddStar addStar, Target target) {
mLifecycle = lifecycle;
mGetEntryCommentList = getEntryCommentList;
mAddStar = addStar;
…
public void getNextPage() {
mPager
.next()
.subscribe(
mPages::onNext,
mErrors::onNext
);
}
public final class CommentViewModel {
public final String profileImageUrl;
public final String username;
public final String comment;
public final List<String> tags;
public final String timestamp;
public ObservableList<HatenaStar> stars;
private final AddStar mAddStar;
private final PublishSubject<Throwable> mErrors;
public CommentViewModel(
String entryId,
BookmarkComment bookmarkComment,
AddStar addStar,
PublishSubject<Throwable> errorsSubject)
{
…
}
public void addStar() {
// 별 추가중의 표시
HatenaStar requesting = new HatenaStar(HatenaStar.Color.REQUESTING, null, null, true);
stars.add(requesting);
mAddStar.execute(mEntryStarInfo)
.subscribe(
() -> {
stars.set(stars.size() - 1, new HatenaStar(HatenaStar.Color.YELLOW, null, null, true));
},
e -> {
mErrors.onNext(new AddStarFailed());
stars.remove(requesting);
}
);
}
…
<FrameLayout
android:id=“@+id/add_star_button”
…
android:onClick="@{() -> viewModel.addStar()}">
</FrameLayout>
<com.hatena.android.view.star.HatenaStarsView
android:id=“@+id/star_view"
…
app:hatenaStars=“@{viewModel.stars}"/>
…
Navigator
public interface LoginNavigator {
void openLogin();
}
public interface LoginNavigator {
void openLogin();
@dagger.Module
class Module {
private final LoginNavigator mNavigator;
public Module(LoginNavigator navigator) {
mNavigator = navigator;
}
@Provides
public LoginNavigator provideNavigator() {
return mNavigator;
}
}
public class TopicFragment extends BaseFragment {
private final LoginNavigator mNavigator =
() -> startActivity(
new Intent(getActivity(),
LoginActivity.class)
);
…
@Nullable
@Override
public View onCreateView(...) {
((App) getActivity().getApplication())
…
.topicFragmentComponent()
.loginNavigatorModule(
new LoginNavigator.Module(mNavigator)
)
.build()
.inject(this);
public class TopicViewModel {
…
private final LoginNavigator mNavigator;
public final ObservableField<View.OnClickListener>
onClickListener = new ObservableField<>(
new View.OnClickListener(){
@Override
public void onClick(View view) {
mNavigator.openLogin();
}
});
<TextView
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{viewModel.onClickListener}"
android:text="ログイン画面へ"/>
설계의 문서화
착수
견적
릴리즈
정리
comments powered by Disqus
Subscribe to this blog via RSS.