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

How to build a messenger V3.0

How to build a messenger V3.0

Andrii Rakhimov

November 03, 2019
Tweet

More Decks by Andrii Rakhimov

Other Decks in Programming

Transcript

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

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

    Delivery status ➔ Typing ➔ Online status ➔ Pagination ➔ Offline send ➔ Delete chat ➔ Blacklist user Features
  3. ➔ 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
  4. ➔ 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
  5. ➔ 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
  6. Battery concerns val lifecycleObserver = object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_START)

    fun onEnterForeground() { socketClient.connect() } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onEnterBackground() { socketClient.disconnect() } } ProcessLifecycleOwner.get().lifecycle.addObserver(lifecycleObserver)
  7. Auth val authListener = object : AuthListener { override fun

    logIn() { socketClient.connect() } override fun logOut() { socketClient.disconnect() } } userOperations.register(authListener)
  8. FCM val fcmListener = object : MessageListener { override fun

    onMessageReceived() { socketClient.connect() } } fcmOperations.register(fcmListener)
  9. Auth val authListener = object : AuthListener { override fun

    logIn() { socketClient.connect() } override fun logOut() { socketClient.disconnect() //more things to do... } } userOperations.register(authListener)
  10. State machines sealed class LifecycleState { object AppStarted: LifecycleState() object

    AppEnteredForeground: LifecycleState() object AppEnteredBackground: LifecycleState() object LoggedIn: LifecycleState() object LoggedOut: LifecycleState() object Reconnected: LifecycleState() object Disconnected : LifecycleState() data class MessageReceived(val message: Any): LifecycleState() }
  11. State machines private val authListener = object : AuthListener {

    override fun logIn() { lifecycleListener.onStateChanged(LifecycleState.LoggedIn) } override fun logOut() { lifecycleListener.onStateChanged(LifecycleState.LoggedOut) } } authOperations.register(authListener)
  12. State machines fun onStateChanged(state: LyfecycleState) { when(state){ Reconnected ->... AppStarted

    -> { socketClient.connect() unreadCounterOperations.syncUnreadCounter()... } AppEnteredForeground ->... AppEnteredBackground -> { socketClient.disconnect()... } LoggedIn ->... }
  13. ➔ 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
  14. MVP

  15. MVP

  16. MVP

  17. MVP

  18. StateMachine Actions //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() }
  19. 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() }
  20. StateMachine Setup class PaginationStateMachine @Inject constructor( private val githubOperations: GithubOperatinos)

    { val input: PublishSubject<Action> = PublishSubject.create() val state: Observable<State> = input .reduxStore( initialState = State.LoadingFirstPageState, sideEffects = listOf( ::loadFirstPageSideEffect, ::loadNextPageSideEffect, ::showAndHideLoadingErrorSideEffect ), reducer = ::reducer )
  21. StateMachine Side Effect 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 was 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)) } }
  22. StateMachine 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) } }
  23. 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
  24. DistinctUntilChanged val state: Observable<State> = input .reduxStore( initialState = State.LoadingFirstPageState,

    sideEffects = listOf( ::loadFirstPageSideEffect, ::loadNextPageSideEffect, ::showAndHideLoadingErrorSideEffect ), reducer = ::reducer ) .distinctUntilChanged()
  25. 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; }); } }