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

How to build a messenger(revised)

How to build a messenger(revised)

Andrii Rakhimov

September 10, 2019
Tweet

More Decks by Andrii Rakhimov

Other Decks in Programming

Transcript

  1. ➔ Story ➔ Transport ➔ Properties of transport and protocol

    ➔ Architecture ➔ MVP & MVI ➔ Learnings Agenda
  2. Features ➔ Real time send/delivery 1:1, 1:n ➔ Image/File sending

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

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

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

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

    Delivery status ➔ Typing ➔ Online status ➔ Pagination ➔ Offline send ➔ Delete chat ➔ Blacklist user Features
  7. ➔ 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
  8. ➔ 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
  9. ➔ 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
  10. HTTP + Simple and widely used - Reopening connection is

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

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

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

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

    Emulation of bidirectional communication - Third party service - HTTP downsides
  15. Socket.IO (Websocket under the hood) + Fast + Bidirectional +

    Support web + Do not waste connection and resources
  16. 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
  17. 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
  18. 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(); } });
  19. Battery concerns ➔ Don’t keep socket connection open, after user

    leaves the app ➔ Don’t use foreground Service to keep connection open
  20. 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
  21. 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
  22. 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; }); } }
  23. How to choose and design a transport? ➔ Determine your

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

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

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

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

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

    business needs and choose appropriate transport ◆ Time ◆ Speed ◆ Security ◆ Features ◆ Web support
  29. 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)
  30. MVP issues ➔ Many events can trigger same UI changes

    as result broken UI ➔ Scales badly on huge and complex screens, presenters polluted with logic
  31. 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
  32. /** * 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
  33. 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() }
  34. 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 )
  35. 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)) } }
  36. 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) } }
  37. 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
  38. DistinctUntilChanged val state: Observable<State> = input .reduxStore( initialState = State.LoadingFirstPageState,

    sideEffects = listOf( ::loadFirstPageSideEffect, ::loadNextPageSideEffect, ::showAndHideLoadingErrorSideEffect ), reducer = ::reducer ) .distinctUntilChanged()
  39. Learnings ➔ Fair estimates ➔ Use appropriate technology for the

    task ➔ Design for flexibility ➔ Start small and validate hypothesis early
  40. Learnings ➔ Fair estimates ➔ Use appropriate technology for the

    task ➔ Design for flexibility ➔ Start small and validate hypothesis early