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

Shared Mobile Architecture @ DAUG Meetup

Shared Mobile Architecture @ DAUG Meetup

remcomokveld

June 08, 2017
Tweet

More Decks by remcomokveld

Other Decks in Technology

Transcript

  1. OVERVIEW • About The Capitals • Past and current Android

    architecture • New structure • Decomposition • Sharing code
  2. OVERVIEW • About The Capitals • Past and current Android

    architecture • New structure • Decomposition • Sharing code
  3. THE CAPITALS • Mobile Agency in Amsterdam Noord • Mobile

    development since 2005 • Builds native Android and iOS apps for startups and bigger clients • Teams of approx. 5 for Android, iOS, Backend and Design. • Still in search of the perfect app architecture
  4. OVERVIEW • About The Capitals • Past and current Android

    architecture • New structure • Decomposition • Sharing code
  5. ARCHITECTURE — HOW WE PROGRESSED • Started with Activities and

    Fragments with lots of responsibilities • Extracted network and database transactions to Manager classes • Moved to MVP
  6. WHAT’S LEFT? • Android and iOS apps have more differences

    than desired • Teams work independently • The same problem is solved twice
  7. RESEARCH PROJECT: THESIS Using a shared Software Architecture in Android

    and iOS apps leads to easier communication, fewer differences and less double work.
  8. OVERVIEW • About The Capitals • Past and current Android

    architecture • New structure • Decomposition • Sharing code
  9. STEVE VESTAL (1993) “An Architecture Description Language is a languages

    whose expressive power is focused on capturing the variations between products in a specific domain”
  10. ROBERT C. MARTIN — SCREAMING ARCHITECTURE (2011) “A good software

    architecture allows decisions about frameworks, databases, web-servers, and other environmental issues and tools, to be deferred and delayed”
  11. SOME RESEARCH • 100 open source android apps • 73

    were packaged based on class type • Android Studio APK analyser • Selection of top apps
  12. Advantages Disadvantages Easier to find code Data, Domain and Presentation

    in one layer Deferred Framework decisions MV* independent Extract classes
  13. OVERVIEW • About The Capitals • Past and current Android

    architecture • New structure • Decomposition • Sharing code
  14. USE-CASE DRIVEN ARCHITECTURE • Modules should be small and easy

    • Module dependency must be uni-directional
  15. DECOMPOSE INTO SMALL MODULES • Split app into feature modules

    • Split feature modules into view modules • A view is always either: • a simple view • a complex view • a collection of other views
  16. EXAMPLE OF A COLLECTION OF VIEWS public class HomeAdapter {

    static final int VIEWTYPE_EVENTS_UPCOMING = 0; static final int VIEWTYPE_EVENTS_RESULTS = 1; static final int VIEWTYPE_NEWS_HEADER = 2; static final int VIEWTYPE_NEWS = 3; static final int VIEWTYPE_NEWS_MORE = 4; static final int VIEWTYPE_WEATHER_TOP = 5; static final int VIEWTYPE_WEATHER_ITEM = 6; …… }
  17. DECOMPOSITION OF DASHBOARD VIEW class HomeAdapter(val lifecycleOwner: LifecycleOwner, val viewModels:

    List<ViewModel>) : RecyclerView.Adapter<ViewModelViewHolder<ViewModel>>() { companion object { val VIEW_TYPE_EVENTS = R.layout.home_events val VIEW_TYPE_WEATHER = R.layout.home_weather val VIEW_TYPE_NEWS = R.layout.home_news } override fun onBindViewHolder(holder: ViewModelViewHolder<*>, position: Int) { val viewModel = viewModels[position] when (holder) { is EventsViewHolder -> holder.bind(viewModel as EventsViewModel) is NewsViewHolder -> holder.bind(viewModel as NewsViewModel) is WeatherViewHolder -> holder.bind(viewModel as WeatherViewModel) } } override fun onViewRecycled(holder: ViewModelViewHolder<*>?) { super.onViewRecycled(holder) holder?.release() } override fun getItemCount(): Int = 4 }
  18. NEWSVIEWHOLDER class NewsViewHolder(val lifecycleOwner: LifecycleOwner, val parent: ViewGroup, val adapter

    = NewsAdapter()) : ViewModelViewHolder<NewsViewModel>(parent.inflateNewChild(R.layout.home_news)) { val observer = Observer<List<NewsArticle>?> { adapter.newsArticles = it ?: emptyList<NewsArticle>() adapter.notifyDataSetChanged() } val recyclerView = itemView.findViewById(R.id.recycler) as RecyclerView var newsArticles: LiveData<List<NewsArticle>>? = null init { recyclerView.adapter = adapter } override fun bind(viewModel: NewsViewModel) { newsArticles = viewModel.getNewsArticles() newsArticles.observe(lifecycleOwner, observer) } override fun release() { super.release() newsArticles?.removeObserver(observer) } }
  19. OVERVIEW • About The Capitals • Past and current Android

    architecture • New structure • Decomposition • Sharing code
  20. class LinkCreatePresenter: BasePresenter<LinkCreateView> { let router: LinkCreateRouter let linkCreateUseCase: LinkCreateUseCase

    init(_ router: LinkCreateRouter, _ linkCreateUseCase: LinkCreateUseCase) { self.router = router self.linkCreateUseCase = linkCreateUseCase } override func onStart() { super.onStart() if self.router.initialDialogViewModel != nil && self.isFirstStart() { self.router.showDialog(viewModel: self.router.initialDialogViewModel!) } self.view?.showSplash(self.router.splash) } func onSendClicked(email: String) { self.view?.setLoading(true) self.linkCreateUseCase.invoke(email: email) { [weak self] (success: Bool) in self?.view?.setLoading(false) if success { self?.onMagicLinkSend() } else { self?.onEmailNotFound() } } } func onMagicLinkSend() { let viewModel = ConfirmationDialogViewModel(…) self.router.showDialog(viewModel: viewModel) } func onEmailNotFound() { let viewModel = ConfirmationDialogViewModel(…) self.router.showDialog(viewModel: viewModel) } } class LinkCreatePresenter : BasePresenter<LinkCreateView> { val router: LinkCreateRouter val linkCreateUseCase: LinkCreateUseCase constructor(router: LinkCreateRouter, linkCreateUseCase: LinkCreateUseCase) { this.router = router this.linkCreateUseCase = linkCreateUseCase } override fun onStart(view: View) { super.onStart() if (isFirstStart()) { this.router.showDialog(initialDialogViewModel) } this.view?.showSplash(router.splash) } fun onSendClicked(email: String) { this.view?.setLoading(true) this.linkCreateUseCase.invoke(email.toString()) { success -> this.view?.setLoading(false) if (success!!) { onMagicLinkSend() } else { onEmailNotFound() } } } private fun onMagicLinkSend() { val viewModel = ConfirmationDialogViewModel(…) router.showDialog(viewModel) } private fun onEmailNotFound() { val viewModel = ConfirmationDialogViewModel(…) this.router.showDialog(viewModel) } } KOTLIN SWIFT