How to build a messenger(revised)

How to build a messenger(revised)

E3b64bb9c6c3768e669c6e85df693c9e?s=128

Andrii Rakhimov

September 10, 2019
Tweet

Transcript

  1. How to build a messenger for Android? Rakhimov Andrii Lead

    Android Engineer at Lalafo andrii.rakhimov@gmail.com
  2. ➔ Story ➔ Transport ➔ Properties of transport and protocol

    ➔ Architecture ➔ MVP & MVI ➔ Learnings Agenda
  3. Story Story

  4. Story

  5. Features ➔ Real time send/delivery 1:1, 1:n

  6. Features ➔ Real time send/delivery 1:1, 1:n ➔ Image/File sending

  7. Features ➔ Real time send/delivery 1:1, 1:n ➔ Image/File sending

    ➔ Delivery status
  8. Features ➔ Real time send/delivery 1:1, 1:n ➔ Image/File sending

    ➔ Delivery status ➔ Typing
  9. Features ➔ Real time send/delivery 1:1, 1:n ➔ Image/File sending

    ➔ Delivery status ➔ Typing ➔ Online status
  10. Features ➔ Real time send/delivery 1:1, 1:n ➔ Image/File sending

    ➔ Delivery status ➔ Typing ➔ Online status ➔ Pagination
  11. Features ➔ Real time send/delivery 1:1, 1:n ➔ Image/File sending

    ➔ Delivery status ➔ Typing ➔ Online status ➔ Pagination ➔ Offline send
  12. ➔ Real time send/delivery 1:1, 1:n ➔ Image/File sending ➔

    Delivery status ➔ Typing ➔ Online status ➔ Pagination ➔ Offline send ➔ Delete chat Features
  13. ➔ Real time send/delivery 1:1, 1:n ➔ Image/File sending ➔

    Delivery status ➔ Typing ➔ Online status ➔ Pagination ➔ Offline send ➔ Delete chat ➔ Blacklist user Features
  14. ➔ Real time send/delivery 1:1, 1:n ➔ Image/File sending ➔

    Delivery status ➔ Typing ➔ Online status ➔ Pagination ➔ Offline send ➔ Delete chat ➔ Blacklist user ➔ Different types of chats ➔ Search Features
  15. ➔ Real time send/delivery 1:1, 1:n ➔ Image/File sending ➔

    Delivery status ➔ Typing ➔ Online status ➔ Pagination ➔ Offline send ➔ Delete chat ➔ Blacklist user ➔ Different types of chats ➔ Search ➔ Chat with Lalafo Features
  16. ➔ Real time send/delivery 1:1, 1:n ➔ Image/File sending ➔

    Delivery status ➔ Typing ➔ Online status ➔ Pagination ➔ Offline send ➔ Delete chat ➔ Blacklist user ➔ Different types of chats ➔ Search ➔ Chat with Lalafo ➔ Automate response ➔ ... Features
  17. Telegram

  18. Telegram Codebase 546 743 LOC

  19. Variety of market solutions

  20. Transport

  21. HTTP

  22. HTTP

  23. HTTP + Simple and widely used

  24. HTTP + Simple and widely used - Reopening connection is

    expensive
  25. HTTP + Simple and widely used - Reopening connection is

    expensive - Unnecessary requests to server
  26. HTTP + Simple and widely used - Reopening connection is

    expensive - Unnecessary requests to server - Slow, not real-time
  27. HTTP + Firebase Cloud Messaging

  28. HTTP + Firebase Cloud Messaging + Close to real-time

  29. HTTP + Firebase Cloud Messaging + Close to real-time +

    Emulation of bidirectional communication
  30. HTTP + Firebase Cloud Messaging + Close to real-time +

    Emulation of bidirectional communication - Third party service
  31. HTTP + Firebase Cloud Messaging + Close to real-time +

    Emulation of bidirectional communication - Third party service - HTTP downsides
  32. Bidirectional transport

  33. Bidirectional transport

  34. Messaging protocols

  35. Socket.IO (Websocket under the hood) + Fast

  36. Socket.IO (Websocket under the hood) + Fast + Bidirectional

  37. Socket.IO (Websocket under the hood) + Fast + Bidirectional +

    Support web
  38. Socket.IO (Websocket under the hood) + Fast + Bidirectional +

    Support web + Do not waste connection and resources
  39. Socket.IO (Websocket under the hood) + Fast + Bidirectional +

    Reconnects + Support web + Do not waste connection and resources - 200+ issues for Android lib, not maintained for 2 years
  40. Best Practices To work With Bidirectional Transport

  41. Acknowledgment

  42. Acknowledgment

  43. Duplicate messages

  44. Duplicate messages

  45. Time sync and message order

  46. Time sync and message order ➔ Validate if time is

    differed widely, and send appropriate time from backend ➔ Store diff on client ➔ Send and show messages accordingly
  47. Battery concerns

  48. Battery concerns ProcessLifecycleOwner.get() .getLifecycle() .addObserver(new LifecycleObserver() { @OnLifecycleEvent(Lifecycle.Event.ON_START) public void

    onEnterForeground() { messengerClient.connect(); } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) public void onEnterBackground() { messengerClient.disconnect(); } });
  49. Battery concerns ➔ Don’t keep socket connection open, after user

    leaves the app
  50. Battery concerns ➔ Don’t keep socket connection open, after user

    leaves the app ➔ Don’t use foreground Service to keep connection open
  51. Battery concerns ➔ Don’t keep socket connection open, after user

    leaves the app ➔ Don’t use foreground Service to keep connection open ➔ Use Firebase Cloud Messaging to notify the app about new messages
  52. Battery concerns ➔ Don’t keep socket connection open, after user

    leaves the app ➔ Don’t use foreground Service to keep connection open ➔ Use Firebase Cloud Messaging to notify the app about new messages ➔ Use WorkManager to prefetch the messages after FCM notification
  53. Startup Time public class App extends MultiDexApplication { … @Override

    public void onCreate() { super.onCreate(); initWhenIdle(); } private void initWhenIdle() { Looper.myQueue().addIdleHandler(() -> { appComponent.socketMessageHandler().start(); return false; }); } }
  54. How to choose and design a transport? ➔ Determine your

    business needs and choose appropriate transport
  55. How to choose and design a transport? ➔ Determine your

    business needs and choose appropriate transport ◆ Time
  56. How to choose and design a transport? ➔ Determine your

    business needs and choose appropriate transport ◆ Time ◆ Speed
  57. How to choose and design a transport? ➔ Determine your

    business needs and choose appropriate transport ◆ Time ◆ Speed ◆ Security
  58. How to choose and design a transport? ➔ Determine your

    business needs and choose appropriate transport ◆ Time ◆ Speed ◆ Security ◆ Features
  59. How to choose and design a transport? ➔ Determine your

    business needs and choose appropriate transport ◆ Time ◆ Speed ◆ Security ◆ Features ◆ Web support
  60. How to choose and design a transport? ➔ Determine your

    business needs and choose appropriate transport ◆ Time ◆ Speed ◆ Security ◆ Features ◆ Web support ➔ Remember about Android(battery, startup time)
  61. Architecture

  62. Architecture ➔ Extensible and composable

  63. Architecture ➔ Extensible and composable ➔ Maintainable

  64. Architecture ➔ Extensible and composable ➔ Maintainable ➔ Reusable

  65. Architecture ➔ Extensible and composable ➔ Maintainable ➔ Reusable ➔

    Testable
  66. Clean Architecture Dependency rule

  67. Architecture

  68. MVP & MVI

  69. MVP & MVI

  70. MVP & MVI

  71. MVP & MVI

  72. MVP issues ➔ Many events can trigger same UI changes

    as result broken UI
  73. MVP issues ➔ Many events can trigger same UI changes

    as result broken UI ➔ Scales badly on huge and complex screens, presenters polluted with logic
  74. MVP issues ➔ Many events can trigger same UI changes

    as result broken UI ➔ Scales badly on huge and complex screens, presenters polluted with logic ➔ Testability
  75. MVP & MVI

  76. MVVM & MVI

  77. MVP & MVI

  78. /** * Input Actions */ sealed class Action { object

    LoadNextPageAction : Action() data class ErrorLoadingPageAction(val error: Throwable, val page: Int) : Action() data class PageLoadedAction(val itemsLoaded: List<GithubRepository>, val page: Int) : Action() data class StartLoadingNextPage(val page: Int) : Action() } StateMachine Actions
  79. StateMachine States sealed class State { object LoadingFirstPageState : State()

    data class ErrorLoadingFirstPageState(val errorMessage: String) : State() data class ShowContentState(val items: List<GithubRepository>, val page: Int) : State() }
  80. StateMachine Setup class PaginationStateMachine @Inject constructor(private val githubOperations: GithubOperatinos) {

    val input: Relay<Action> = PublishRelay.create() val state: Observable<State> = input .reduxStore( initialState = State.LoadingFirstPageState, sideEffects = listOf( ::loadFirstPageSideEffect, ::loadNextPageSideEffect, ::showAndHideLoadingErrorSideEffect ), reducer = ::reducer )
  81. StateMachine Side Effect /** * Load the first Page */

    private fun loadFirstPageSideEffect(actions: Observable<Action>, state: StateAccessor<State>): Observable<Action> { return actions.ofType(Action.LoadFirstPageAction::class.java) .filter { state() !is ContainsItems } // If first page has already been loaded, do nothing .switchMap { val state = state() val nextPage = (if (state is ContainsItems) state.page else 0) + 1 githubOperations.loadRepositories(nextPage) .map<Action> { result -> PageLoadedAction(itemsLoaded = result.items, page = nextPage) } .onErrorReturn { error -> ErrorLoadingPageAction(error, nextPage) } .startWith(StartLoadingNextPage(nextPage)) } }
  82. StateMachine Reducer /** * The state reducer. * Takes Actions

    and the current state to calculate the new state. */ private fun reducer(state: State, action: Action): State { return when (action) { is StartLoadingNextPage -> State.LoadingFirstPageState is PageLoadedAction -> State.ShowContentState(items = action.items, page = action.page) is ErrorLoadingPageAction -> State.ErrorLoadingFirstPageState(action.error.localizedMessage) } }
  83. Displaying state open fun render(state: PaginationStateMachine.State) = when (state) {

    PaginationStateMachine.State.LoadingFirstPageState -> { recyclerView.gone() loading.visible() error.gone() } is PaginationStateMachine.State.ShowContentState -> { showRecyclerView(items = state.items, showLoadingNext = false) } is PaginationStateMachine.State.ErrorLoadingFirstPageState -> { recyclerView.gone() loading.gone() error.visible() snackBar?.dismiss() } } Compose Dreams
  84. Performance matters

  85. DistinctUntilChanged val state: Observable<State> = input .reduxStore( initialState = State.LoadingFirstPageState,

    sideEffects = listOf( ::loadFirstPageSideEffect, ::loadNextPageSideEffect, ::showAndHideLoadingErrorSideEffect ), reducer = ::reducer ) .distinctUntilChanged()
  86. Learnings ➔ Fair estimates

  87. Fair estimates

  88. Learnings ➔ Fair estimates ➔ Use appropriate technology for the

    task
  89. None
  90. Learnings ➔ Fair estimates ➔ Use appropriate technology for the

    task ➔ Design for flexibility
  91. Learnings ➔ Fair estimates ➔ Use appropriate technology for the

    task ➔ Design for flexibility ➔ Start small and validate hypothesis early
  92. Cost of change

  93. Learnings ➔ Fair estimates ➔ Use appropriate technology for the

    task ➔ Design for flexibility ➔ Start small and validate hypothesis early
  94. Thank you! Rakhimov Andrii Lead Android Engineer at Lalafo andrii.rakhimov@gmail.com

    https://github.com/ar-g