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

MyShop Mobile App and Next Tech Stack

MyShop Mobile App and Next Tech Stack

LINE Developers Thailand

October 16, 2021
Tweet

More Decks by LINE Developers Thailand

Other Decks in Technology

Transcript

  1. Songpol Rungsawang


    Software Engineer,


    LINE Thailand
    MyShop


    MOBILE APP &


    NEXT TECH STACK
    Komsit Chusangthong


    Software Engineer,


    LINE Thailand

    View full-size slide

  2. Songpol


    Rungsawang


    Software Engineer,


    LINE Thailand
    Komsit


    Chusangthong


    Software Engineer,


    LINE Thailand
    MyShop MOBILE APP &


    NEXT TECH STACK

    View full-size slide

  3. MyShop Desktop

    View full-size slide

  4. CHAT NOTIFICATION ORDERS PRODUCT
    Features

    View full-size slide

  5. Jetpack Compose for Building UI

    View full-size slide

  6. What is Jetpack Compose?
    “Jetpack Compose is Android’s modern toolkit for building native UI.


    It simplifies and accelerates UI development on Android.


    Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin
    APIs.”
    https://developer.android.com/jetpack/compose

    View full-size slide

  7. Jetpack Compose is now 1.0
    Since 28 July 2021

    View full-size slide

  8. Why do we adopt ?

    View full-size slide

  9. Build entirely in Kotlin

    View full-size slide

  10. Can Integrate with a View-Based UI
    Views
    Compose
    Views
    Compose
    Compose

    View full-size slide

  11. Built-in Support for Material Design, Dark Theme,


    Animation and more
    https://material.io/blog/jetpack-compose-beta

    View full-size slide

  12. Support Material You is In Focus
    Jetpack Compose Roadmap: https://developer.android.com/jetpack/androidx/compose-roadmap
    ‘In Focus’ items are being worked on soon and are likely to land in an upcoming stable release.

    View full-size slide

  13. Basic Concept
    State 1 UI 1
    State 2 UI 2

    View full-size slide

  14. State 1 UI 1
    State 2 UI 2
    Recomposition

    View full-size slide

  15. Example
    Image Text
    Text

    View full-size slide

  16. Recomposition
    @Composable


    fun ChatItem(


    pictureUrl: String,


    name: String,


    lastMessage: String


    ) {





    }
    Imag Tex
    Tex

    View full-size slide

  17. Recomposition
    @Composable


    fun ChatItem(


    pictureUrl: String,


    name: String,


    lastMessage: String // Change


    ) {





    }
    Imag Tex
    Tex

    View full-size slide

  18. Recomposition
    @Composable


    fun ChatItem(





    lastMessage: String


    ) {





    Column(





    ) {





    CompositionLocalProvider(LocalContentAlpha provides
    ContentAlpha.medium) {


    Text(text = lastMessage, style = MaterialTheme.typography.body2)


    }


    }


    }
    Imag Tex
    Tex

    View full-size slide

  19. Managing Compose State

    View full-size slide

  20. Stateful With Remember
    @Composable


    fun WelcomeContent() {


    Column(modifier = Modifier.padding(16.dp)) {


    val (name, setName) = remember { mutableStateOf("") }





    }


    }
    • Hold and modify its state internally


    • Help persist state by


    remember : from re-compositions


    rememberSaveable : from con
    fi
    guration changes


    • Caller can use without managing

    the state themselves

    View full-size slide

  21. Stateless With State Hoisting
    class WelcomeViewModel : ViewModel()
    {

    private val _name = MutableLiveData("")
    val name: LiveData = _nam
    e

    fun onNameChange(newName: String) {
    _name.value = newName
    }
    }
    Use ViewModel to hold state and handle event

    View full-size slide

  22. Stateless With State Hoisting
    @Composabl
    e

    fun WelcomeScreen(welcomeViewModel: WelcomeViewModel = viewModel())
    {

    val name: String by welcomeViewModel.name.observeAsState("")
    WelcomeContent(name, onNameChange = { welcomeViewModel.onNameChange(it) })
    }
    Observe value as state in composable function

    View full-size slide

  23. Stateless With State Hoisting
    State Hoisting : Pattern where we move the state to the
    caller by replace the internal state with two parameters


    value: T value to display


    onValueChange: (T) -> Unit event when value change
    @Composabl
    e

    fun WelcomeContent
    (

    name: String,
    onNameChange: (String) -> Unit
    )
    {

    Column(modifier = Modifier.padding(16.dp))
    {

    Text
    (

    text = "Welcome, $name! to Compose"
    ,


    )

    OutlinedTextField(
    onValueChange = { onNameChange(it) }
    ,


    )

    }

    }

    View full-size slide

  24. State Flow in … Event Flow out

    View full-size slide

  25. State Flow in
    class AnnouncementViewModel
    (



    ) : AndroidViewModel(application) {
    private val _announcement = MutableStateFlow(null)
    val announcement: StateFlow = _announcement
    private val _isShow = MutableStateFlow(false)
    val isShow: StateFlow = _isShow
    private val _isDoNotShowAgain = MutableStateFlow(false)
    val isDoNotShowAgain: StateFlow = _isDoNotShowAgain

    }

    View full-size slide

  26. State Flow in
    @Composabl
    e

    fun SingleAnnouncementModal
    (

    announcementViewModel: AnnouncementViewMode
    l

    )
    {

    val announcement = announcementViewModel.announcement.collectAsState()
    val isShow: Boolean by announcementViewModel.isShow.collectAsState()
    val isDoNotShowAgain: Boolean by announcementViewModel.isDoNotShowAgain.collectAsState(
    )

    SingleAnnouncementModal(
    isShow = isShow,
    announcement = announcement.value,
    isDoNotShowAgain = isDoNotShowAgain,
    onImageClicked = { announcementViewModel.onImageClicked() },
    onCloseClicked = { announcementViewModel.onCloseClicked() },
    onBackPressed = { announcementViewModel.onBackPressed() },
    onDoNotShowAgainClicked = { announcementViewModel.onDoNotShowAgainClicked() }
    )

    }

    View full-size slide

  27. State Flow in
    @Composabl
    e

    fun SingleAnnouncementModal
    (

    isShow: Boolean,
    announcement: AnnouncementUiModel?,
    isDoNotShowAgain: Boolean,
    onImageClicked: () -> Unit,
    onCloseClicked: () -> Unit,
    onBackPressed: () -> Unit,
    onDoNotShowAgainClicked: () -> Unit
    )
    {

    if (!isShow || announcement == null || announcement.imageUrl.isBlank()) return
    }

    View full-size slide

  28. State Flow in
    @Composabl
    e

    fun SingleAnnouncementModal
    (

    ..
    .

    )
    {


    Dialog
    (


    )
    {


    IconButton(
    onClick = {

    onCloseClicked()
    }
    ) {
    Icon(

    painterResource(id = R.drawable.icon_close_24px),
    )
    }


    }
    }

    View full-size slide

  29. State Flow in
    @Composabl
    e

    fun SingleAnnouncementModal
    (

    ..
    .

    )
    {


    Dialog
    (


    )
    {


    val painter = rememberImagePainter(

    )
    Image(

    painter = painter,
    modifier = Modifier

    .clickable(

    ) {

    onImageClicked()
    },
    )
    ..
    .

    }
    }

    View full-size slide

  30. State Flow in
    @Composabl
    e

    fun SingleAnnouncementModal
    (

    ..
    .

    )
    {


    Dialog
    (


    )
    {


    Checkbox(

    checked = isDoNotShowAgain,
    onCheckedChange = {
    onDoNotShowAgainClicked()
    },
    ){
    Text(

    text = stringResource(id = R.string.ecnotification_action_dontshowagain_text),
    )

    }


    }
    }

    View full-size slide

  31. Event Flow out
    @Composabl
    e

    fun SingleAnnouncementModal
    (

    onCloseClicked: () -> Unit,
    )
    {


    Dialog
    (


    )
    {


    IconButton(
    onClick =
    {


    onCloseClicked()
    }

    )
    {


    }


    }
    }

    View full-size slide

  32. State Flow in
    @Composabl
    e

    fun SingleAnnouncementModal
    (

    announcementViewModel: AnnouncementViewMode
    l

    )
    {



    SingleAnnouncementModal
    (


    onCloseClicked = { announcementViewModel.onCloseClicked() },
    )


    }

    View full-size slide

  33. Event Flow out
    class AnnouncementViewModel
    (



    ) : AndroidViewModel(application)
    {

    private val _isShow = MutableStateFlow(false)
    val isShow: StateFlow = _isShow




    fun onCloseClicked() {
    _isShow.value = false

    }



    }

    View full-size slide

  34. credit: https://clean-swift.com

    View full-size slide

  35. Why Clean Swift?
    Protocol /
    Interface
    Unit Test Very Clear
    Easy Maintenance
    Reuse
    Logic

    View full-size slide

  36. Clean Swift Diagram
    PRESENTER
    INTERACTOR
    VIEW VIEW


    ROUTER
    WORKER
    DATA
    SOURCE

    View full-size slide

  37. Example ViewController
    protocol WelcomePageDisplayLogic: class {


    func displayShowError()


    }


    fi
    nal class WelcomePageViewController: BaseViewController, WelcomePageDisplayLogic {


    var interactor: WelcomePageBusinessLogic?


    var router: (NSObjectProtocol & WelcomePageRoutingLogic & WelcomePageDataPassing)?


    // MARK: Object lifecycle


    // ... init method


    // MARK: View lifecycle




    override func viewDidLoad() {


    super.viewDidLoad()




    interact?.fetch()


    }


    func displayShowError() {


    router?.routeToErrorPage()


    }


    }

    View full-size slide

  38. Example Interactor
    protocol WelcomePageBusinessLogic {


    func fetch()


    }


    protocol WelcomePageDataStore {


    }


    fi
    nal class WelcomePageInteractor: WelcomePageBusinessLogic, WelcomePageDataStore {


    var presenter: WelcomePagePresentationLogic?


    lazy var worker: WelcomePageWorkerable? = {


    return WelcomePageWorker()


    }()


    func fetch() {


    if worker?.fetchAPI() {


    presenter?.presentShowError()


    }


    }


    }

    View full-size slide

  39. Example Worker
    protocol WelcomePageWorkable {


    func fetchAPI()


    }


    fi
    nal class WelcomePageWorker: WelcomePageWorkable {


    func fetchAPI()
    ->
    Bool {


    return true


    }


    }

    View full-size slide

  40. Example Presenter
    protocol WelcomePagePresentationLogic {


    func presentShowError()


    }


    fi
    nal class WelcomePagePresenter: WelcomePagePresentationLogic {


    weak var viewController: WelcomePageDisplayLogic?


    func presentShowError() {


    viewController?.displayShowError()


    }


    }

    View full-size slide

  41. Example Router
    @objc protocol WelcomePageRoutingLogic {


    func routeToErrorPage()


    }


    protocol WelcomePageDataPassing {


    var dataStore: WelcomePageDataStore? { get }


    }


    fi
    nal class WelcomePageRouter: NSObject, WelcomePageRoutingLogic, WelcomePageDataPassing
    {


    weak var viewController: WelcomePageViewController?


    var dataStore: WelcomePageDataStore?




    // MARK: Routing


    func routeToErrorPage() {


    //TODO handle navigate vc


    }


    }

    View full-size slide

  42. Coordinator
    https://www.iamin.dev/Deeplink-with-coordinators

    View full-size slide

  43. Coordinator Diagram
    VC A
    COORDINATOR
    VC B VC C

    View full-size slide

  44. Why Coordinator?
    No Dependency
    Reusable Code
    Easy Maintenance

    View full-size slide

  45. Example
    fi
    nal class AppCoordinator: Coordinator {


    var childCoordinators = [Coordinator]()


    var navigationController: UINavigationController


    // ... init method


    func goToWelcomePage() {


    let welcomePageVC = WelcomePageViewController()


    welcomePageVC.coordinator = self


    guard var destinationDS = welcomePageVC.router?.dataStore else { return }


    guard let usecaseProvider = usecaseProvider else { return }


    passDataToWelcomePage(usecaseProvider: usecaseProvider, sourceVC: sourceVC, destination:
    &destinationDS)


    navigationController.pushViewController(welcomePageVC, animated: true)


    }


    private func passDataToWelcomePage(usecaseProvider: OAPUseCaseProvider, sourceVC:
    UIViewController?, destination: inout WelcomeDataStore) {


    destination.usecaseProvider = usecaseProvider


    if let sourceVC = sourceVC, let nav = sourceVC.navigationController {


    destination.isNavigationBarHidden = nav.isNavigationBarHidden


    }


    }


    }

    View full-size slide

  46. Example
    fi
    nal class AcceptConsentRouter: NSObject,
    AcceptConsentRoutingLogic, AcceptConsentDataPassing {


    weak var viewController: AcceptConsentViewController?


    var dataStore: AcceptConsentDataStore?


    // MARK: Routing


    func routeToWelcomePage() {


    if let coordinator = viewController?.coordinator {


    coordinator.goToWelcomePage()


    }


    }


    func routeBack(completion: (()
    ->
    Void)? = nil) {


    if let coordinator = viewController?.coordinator {


    coordinator.didFinishConsent(sourceVC:
    viewController, completion: completion)


    }


    }


    }
    INTERACTOR
    COORDINATOR
    VIEW


    CONTROLLER
    ROUTER
    WORKER
    DATA SOURCE
    VIEW
    PRESENTER

    View full-size slide

  47. Before We Go…

    View full-size slide