Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Многоразовые компоненты Android-приложений Badoo. От копипасты к модулям

Многоразовые компоненты Android-приложений Badoo. От копипасты к модулям

У Badoo и других приложений компании есть долгая история использования одной и той же базовой платформы. Эксперименты с поддержкой различных приложений с разным набором плюшек начались достаточно давно: пробовали использовать одну кодбазу с разным набором условий или просто копипастить. В конечном итоге все это выливалось в трудности с поддержкой и внедрением новых фич.

И вот на горизонте появился запуск нового продукта, кардинально отличающегося от текущих, но в то же время сильно похожего.

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

Andrei Shikov

April 22, 2019
Tweet

Other Decks in Technology

Transcript

  1. 3

  2. Cool stuff Repo A Cool stuff Repo A Cool stuff

    Repo B " Andrei's stuff Cool stuff Repo A App App 11
  3. 23

  4. Lib Repo A App Cool stuff Repo B App 30

    www.youtube.com/watch?v=V_lBeu4Xcp0
  5. Что дает монорепо? 1. Переиспользование кода 2. Общий тулчейн 3.

    Отсутствие модификаций 4. Нет барьера – Один большой репозиторий 36
  6. // input = Consumer<T> // output = ObservableSource<T> class View(

    val events: PublishRelay<Event> ): ObservableSource<Event> by events, Consumer<ViewModel> { val button: Button val textView: TextView } 51
  7. // input = Consumer<T> // output = ObservableSource<T> class View(

    val events: PublishRelay<Event> ): ObservableSource<Event> by events, Consumer<ViewModel> { val button: Button val textView: TextView init { button.setOnClickListener { events.accept(Event.ButtonClick) } } } 52
  8. // input = Consumer<T> // output = ObservableSource<T> class View(

    val events: PublishRelay<Event> ): ObservableSource<Event> by events, Consumer<ViewModel> { val button: Button val textView: TextView init { button.setOnClickListener { events.accept(Event.ButtonClick) } } override fun accept(model: ViewModel) { textView.text = model.text } } 53
  9. // input = Consumer<T> // output = ObservableSource<T> class Feature:

    ReducerFeature<Wish, State>( initialState = State(counter = 0), reducer = ReducerImpl() ) { class ReducerImpl: Reducer<Wish, State> { override fun invoke(wish: Wish) = when (wish) { is Increment -> } } } 54
  10. // input = Consumer<T> // output = ObservableSource<T> class Feature:

    ReducerFeature<Wish, State>( initialState = State(counter = 0), reducer = ReducerImpl() ) { class ReducerImpl: Reducer<Wish, State> { override fun invoke(wish: Wish) = when (wish) { is Increment -> state.copy(counter = state.counter + 1) } } } 55
  11. View Feature Event State Wish ViewModel 57 val eventToWish: (Event)

    -> Wish = { when (it) { is ButtonClick -> Increment } } val stateToModel: (State) -> ViewModel = { ViewModel(text = state.counter.toString()) }
  12. 58 val eventToWish: (Event) -> Wish = { when (it)

    { is ButtonClick -> Increment } } val stateToModel: (State) -> ViewModel = { ViewModel(text = state.counter.toString()) } Binder().apply { bind(view to feature using eventToWish) bind(feature to view using stateToModel) } View Feature Event State Wish ViewModel
  13. 65

  14. 70

  15. 71

  16. object SomeScopedComponent : ScopedComponent<SomeComponent>() { override fun create(): SomeComponent {

    return DaggerSomeComponent.builder() .build() } override fun SomeComponent.subscribe(): Array<Disposable> = arrayOf( Binder().apply { bind(feature().news to otherFeature()) bind(feature() to view()) } ) } 77
  17. DI работает с логикой 78 fun Component.subscribe() { Binder().apply {

    bind(feature1().news to otherFeature()) bind(feature2() to feature3()) bind(otherFeature().news to feature1()) bind(feature1() to view()) bind(someListener to feature3) bind(dataObservable to otherFeature()) bind(feature4() to feature1()) } }
  18. 79 with(binder) { val uiEvents = combinedUiEvents(view) val photoUploadHandler =

    MultiplePhotoUploadHandler( contextWrapper.context as Activity, contextWrapper.lifecycleEventsRx2() ) bind(encounterEvents to encounterAnalyticsTracker) bind(uiEvents to encounterScreenAnalyticsTracker) bind(combinedViewModels to view) bind(voteInitiations to voteController named "SparkDebug.Vote bind(voteController to voteTutorialDialogHandler using Encoun bind(voteTutorialDialogHandler to view using EncountersEventT bind(voteTutorialDialogHandler to voteController) bind(voteControllerVotesResolved to voteProcessor using voteE bind(voteProcessor to encountersFeature using VoteResultToEnc bind(encounterEvents to encountersFeature using EncountersEve bind(uiEvents to modeTabFeature using EncountersScreenEventTo bind(modeTabFeature.news to encountersFeature using ModeTabsN bind(encounterEvents to encounterServerStats) bind(blockEvents to BlockOrReportHandler(view, contextWrapper bind(matches to matchScreenFeature) bind(uiEvents to photoUploadHandler using EncountersScreenEve bind(photoUploadHandler to photoStatusFeature using { PhotoStatusFeature.Wish.NotifyPhotoUpload }) bind(locationUpdates to encountersFeature) if (BuildVariant.INFO) { bind(debugCommands to encountersFeature) } }
  19. 80 DI Scope Компонент View Logic Logic Output Input Компонент

    соединяет логику Input/Output как в MVICore
  20. 81 @Scope internal class ComponentImpl @Inject constructor( private val params:

    ScreenParams, news: NewsRelay, @OnDisposeAction onDisposeAction: () -> Unit, globalFeature: GlobalFeature, conversationControlFeature: ConversationControlFeature messageSyncFeature: MessageSyncFeature, conversationInfoFeature: ConversationInfoFeature, conversationPromoFeature: ConversationPromoFeature, messagesFeature: MessagesFeature, messageActionFeature: MessageActionFeature, initialScreenFeature: InitialScreenFeature, initialScreenExplanationFeature: InitialScreenExplanat errorFeature: ErrorFeature, conversationInputFeature: ConversationInputFeature, sendRegularFeature: SendRegularFeature, sendContactForCreditsFeature: SendContactForCreditsFea screenEventTrackingFeature: ScreenEventTrackingFeature messageReadFeature: MessageReadFeature?, messageTimeFeature: MessageTimeFeature?, photoGalleryFeature: PhotoGalleryFeature?, onlineStatusFeature: OnlineStatusFeature?, favouritesFeature: FavouritesFeature?, isTypingFeature: IsTypingFeature?, giftStoreFeature: GiftStoreFeature?, messageSelectionFeature: MessageSelectionFeature?, reportingFeature: ReportingFeature?, takePhotoFeature: TakePhotoFeature?, giphyFeature: GiphyFeature, goodOpenersFeature: GoodOpenersFeature?, matchExpirationFeature: MatchExpirationFeature, private val pushIntegration: PushIntegration ) : AbstractMviComponent<UiEvent, States>( This is fine
  21. 84 DI Компонент Logic Logic Output Input DI Компонент Logic

    Logic Output Input DI Компонент Logic Logic Output Input Два дерева – View и логическое
  22. 85 DI Компонент View Logic Logic Output Input DI Компонент

    View Logic Logic Output Input DI Компонент View Logic Logic Output Input Два дерева – View и логическое
  23. 87 Компонент DI View Logic Logic Input Output View внутри

    Логика внутри Взаимодействие через 
 source/consumer
  24. В итоге: Копипаста – многоуровневая проблема Процессы – основа для

    переиспользования Архитектура – модульно и совместимо 110
  25. В итоге: Копипаста – многоуровневая проблема Процессы – основа для

    переиспользования Архитектура – модульно и совместимо Переиспользовать код – реально 111