Shared Mobile Architecture @ DAUG Meetup

Shared Mobile Architecture @ DAUG Meetup

856e42fdebde4aed59621710ef899984?s=128

remcomokveld

June 08, 2017
Tweet

Transcript

  1. SHARED MOBILE ARCHITECTURE BY REMCO MOKVELD

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

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

    architecture • New structure • Decomposition • Sharing code
  4. 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
  5. OVERVIEW • About The Capitals • Past and current Android

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

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

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

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

    architecture • New structure • Decomposition • Sharing code
  11. TALON FOR TWITTER

  12. OLD PACKAGE STRUCTURE

  13. 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”
  14. 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”
  15. SOME RESEARCH • 100 open source android apps • 73

    were packaged based on class type • Android Studio APK analyser • Selection of top apps
  16. APPS WITH FEATURE BASED PACKAGING

  17. WHATSAPP

  18. RUNKEEPER

  19. OLD PACKAGE STRUCTURE

  20. FEATURE BASED STRUCTURE

  21. Advantages Disadvantages Easier to find code Data, Domain and Presentation

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

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

    • Module dependency must be uni-directional
  24. 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
  25. 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; …… }
  26. EXAMPLE OF A COLLECTION OF VIEWS

  27. DECOMPOSITION OF DASHBOARD VIEW

  28. 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 }
  29. 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) } }
  30. OVERVIEW • About The Capitals • Past and current Android

    architecture • New structure • Decomposition • Sharing code
  31. Android and iOS are not that different

  32. 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
  33. TOOLS FOR SHARING CODE • https://github.com/angelolloqui/SwiftKotlin • Skeletal Structure Checker

    (WIP)
  34. THANK YOU QUESTIONS?

  35. Always looking for new talent and expertise, contact jobs@thecapitals.nl if

    you're interested in joining our team!