Giter VIP home page Giter VIP logo

android-actor's Introduction

Android-Actor

Sample app showing one way to implement Actors in kotlin using channels. The class Actor will look like bellow

abstract class Actor<in T> {

	private val job = SupervisorJob()

	protected var scope = CoroutineScope(Dispatchers.IO + job)

	private var channel: Channel<T>? = null

	private var isStarted: AtomicBoolean = AtomicBoolean(false)


	open fun start() {

		if (isStarted.compareAndSet(false, true)) {

			startCoroutineActor()

		}

	}

	fun send(inMsg: T) {

		scope.launch {

			channel?.send(inMsg)

		}

	}

	/**
	 * Common method to not repeat the cancelChildren() call in every subclass.
	 * It will cancel both child Actor-Coroutines the Principal and the Common.
	 * */
	open fun stop() {

		scope.coroutineContext.cancelChildren()

		isStarted.set(false)

	}

  /**
   * This is specifically to Android, it is handy to propagate the Activity.onBackPressed() event to the Actors cluster.
   * It is really don't needed, you can pass the back event as a message to the Actor regular message inbox.
   * */
  open fun back() {}

	fun close() {

		onClose()

		channel?.close()

		scope.cancel()

	}

	private fun startCoroutineActor() {

		channel = Channel()

		scope.launch {

			channel?.consumeEach { msg ->

				onAction(msg)

			}

		}


	}

	protected open fun onClose() {}

	protected abstract fun onAction(inMsg: T)

}

An example of how to use it could be something like bellow.

class AuthPresenterActor(
    private var uiSendChannel: SendChannel<UIActorMsg>
): Actor<AuthPresenterActor.InMsg>() {

    enum class Stage {
        Select,
        Login,
        Signup
    }

    private var stage = Stage.Select

    lateinit var parentChannel: SendChannel<OutMsg>

    lateinit var fragmentChannel: SendChannel<OutMsg.View>

    private val authManager = HamperApplication.instance.authManager


    override fun start() {
        super.start()

        scope.launch {

            checkLoginBefore()
                .collect { loginBefore ->

                    if (loginBefore) {

                        parentChannel.send(OutMsg.Login.AuthSuccess)

                    }
                    else {

                        when (stage) {

                            Stage.Select -> showSelect()

                            Stage.Login -> showLogin()

                            Stage.Signup -> showSignup()

                        }

                    }

                }

        }

    }

    @UseExperimental(InternalCoroutinesApi::class)
    override fun onAction(inMsg: InMsg) {

        when (inMsg) {

            InMsg.View.OnSelectAuthViewReady -> {}

            InMsg.View.OnSelectAuthViewStop -> {}

            InMsg.View.ShowLogin -> { showLogin() }

            InMsg.View.OnLoginViewReady -> {}

            InMsg.View.OnLoginFragmentResult -> {

                scope.launch {

                    checkLoginBefore()
                        .collect { loginBefore ->

                            if (loginBefore) {

                                parentChannel.send(OutMsg.Login.AuthSuccess)

                            }

                        }

                }

            }

            is InMsg.View.DoLogin -> {

                scope.launch {

                    fragmentChannel.send(OutMsg.View.Login.OnLoad)

                    authManager
                        .doLogin(inMsg.loginReq)
                        .catch { th ->

                            // TODO: Make a util class to provide a message per each type of exception.
                            fragmentChannel.send(OutMsg.View.Login.OnError(th))

                        }
                        .collect {

                            fragmentChannel.send(OutMsg.View.Login.OnSuccess)

                            // We send a success to our parent so he will handle the Active stage to other actor
                            parentChannel.send(OutMsg.Login.AuthSuccess)

                        }

                }

            }

            InMsg.View.OnLoginViewStop -> {}

            InMsg.View.ShowSignUp -> { showSignup() }

            InMsg.View.OnSignupViewReady -> { }

            InMsg.View.OnSignupFragmentResult -> {

                scope.launch {

                    checkLoginBefore()
                        .collect { loginBefore ->

                            if (loginBefore) {

                                parentChannel.send(OutMsg.Login.AuthSuccess)

                            }

                        }

                }

            }

            is InMsg.View.DoSignUp -> {

                scope.launch {

                    fragmentChannel.send(OutMsg.View.Signup.OnLoad)

                    authManager
                        .doSignup(inMsg.signupReq)
                        .catch { th ->

                            // TODO: Make a util class to provide a message per each type of exception.
                            fragmentChannel.send(OutMsg.View.Signup.OnError(th))

                        }
                        .collect { resp ->

                            fragmentChannel.send(OutMsg.View.Signup.OnSuccess)

                            // We send a success to our parent so he will handle the Active stage to other actor
                            parentChannel.send(OutMsg.Signup.Success)

                        }

                }

            }

            InMsg.View.OnSignupViewStop -> {}

        }

    }

    override fun back() {
        super.back()

        onBackPressed()
    }

    private fun showSelect() {

        val selectAuthFragment = SelectAuthFragment.newInstance(this)

        val uiMsg = UIActorMsg.SetFragment(selectAuthFragment, "selectAuthFragment")

        scope.launch {

            uiSendChannel.send(uiMsg)

            stage = Stage.Select
        }

    }

    private fun showLogin() {

        val titleMsg = UIActorMsg.SetTitle("Login Screen")

        val loginFragment = LoginFragment.newInstance(this)

        val uiMsg = UIActorMsg.SetFragment(loginFragment, "loginFragment")

        scope.launch {

            uiSendChannel.send(titleMsg)

            uiSendChannel.send(uiMsg)

            stage = Stage.Login
        }

    }

    private fun showSignup() {

        val titleMsg = UIActorMsg.SetTitle("SignUp Screen")

        val signupFragment = SignupFragment.newInstance(this)

        val uiMsg = UIActorMsg.SetFragment(signupFragment, "signupFragment")

        scope.launch {

            uiSendChannel.send(titleMsg)

            uiSendChannel.send(uiMsg)

            stage = Stage.Signup
        }

    }

    private fun onBackPressed() {

        when (stage) {

            Stage.Select -> {

                scope.launch {

                    parentChannel.send(OutMsg.Login.AuthError("Back Pressed Cancelled"))

                }

            }

            Stage.Login -> { showSelect() }

            Stage.Signup -> { showSelect() }

        }

    }

    private fun CoroutineScope.checkLoginBefore(): Flow<Boolean> {

        val hamperAuthToken = authManager.authToken()

        if (hamperAuthToken != null) {

            return flowOf(true)

        }

        if (authManager.verifyFBLogin()) {

            return authManager.doLoginWithFacebookToken()

        }

        return flowOf(false)

    }


    sealed class InMsg {

        sealed class View : InMsg() {

            object OnSelectAuthViewReady : View()

            object OnSelectAuthViewStop : View()

            object ShowLogin : View()

            object OnLoginViewReady : View()

            object OnLoginFragmentResult : View()

            object OnLoginViewStop : View()

            class DoLogin(val loginReq: LoginReq) : View()

            object ShowSignUp : View()

            object OnSignupViewReady : View()

            object OnSignupFragmentResult : View()

            object OnSignupViewStop : View()

            class DoSignUp(val signupReq: SignupReq) : View()

        }

    }

    sealed class OutMsg {

        sealed class Login : OutMsg() {

            object AuthSuccess : Login()

            class AuthError(val error: String) : Login()

        }

        sealed class Signup : OutMsg() {

            object Success : Signup()

            class Error(val error: String) : Signup()

        }

        sealed class View : OutMsg() {

            sealed class Login : View() {

                object OnLoad : Login()

                object OnSuccess : Login()

                class OnError(val th: Throwable) : Login()

            }

            sealed class Signup : View() {

                object OnLoad : Signup()

                object OnSuccess : Signup()

                class OnError(val th: Throwable) : Signup()

            }

        }

    }

}

Check the code for more usage examples.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.