Skip to content

Commit

Permalink
Update lifecyle example (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
pedroql committed Jul 29, 2020
1 parent d92effa commit edf5412
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 192 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build (Compile, lint, checkApi, test, etc)
run: ./gradlew build
run: ./gradlew --no-daemon build
- name: Archive build reports artifacts
if: always()
uses: actions/upload-artifact@v2
Expand All @@ -42,7 +42,7 @@ jobs:
- name: Ensure documentation is up to date
id: doc-check
run: |
./gradlew dokka
./gradlew --no-daemon dokka
# the next command will list documentation files out of date. Please run ./gradlew dokka
git diff --exit-code --name-only || echo ::set-output name=status::failure
- name: Check which documentation files have changed
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build (Compile, lint, checkApi, test, etc)
run: ./gradlew -DVERSION_OVERRIDE=${{ steps.parse_tag.outputs.VERSION }} build
run: ./gradlew --no-daemon -DVERSION_OVERRIDE=${{ steps.parse_tag.outputs.VERSION }} build
- name: Archive build reports artifacts
if: always()
uses: actions/upload-artifact@v2
Expand All @@ -47,7 +47,7 @@ jobs:
- name: Ensure documentation is up to date
id: doc-check
run: |
./gradlew dokka
./gradlew --no-daemon dokka
# the next command will list documentation files out of date. Please run ./gradlew dokka
git diff --exit-code --name-only || echo ::set-output name=status::failure
- name: Check which documentation files have changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,106 +3,91 @@ package net.pedroloureiro.mvflow.samples.android.screens.lifecycle
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.util.Log
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.broadcast
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.channels.ticker
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch
import net.pedroloureiro.mvflow.MVFlow
import net.pedroloureiro.mvflow.samples.android.databinding.LifecycleActivityBinding
import net.pedroloureiro.mvflow.samples.android.screens.dummydialog.DummyDialogActivity
import net.pedroloureiro.mvflow.samples.android.screens.lifecycle.LifecycleMVFlow.Action
import net.pedroloureiro.mvflow.samples.android.screens.lifecycle.LifecycleMVFlow.Effect
import net.pedroloureiro.mvflow.samples.android.screens.lifecycle.LifecycleMVFlow.State

class LifecycleActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
logLifeCycle(if (savedInstanceState == null) "onCreate" else "onCreate with saved instance state")
title = "Advanced lifecycle"
val binding = LifecycleActivityBinding.inflate(layoutInflater)
setContentView(binding.root)

val view = object : MVFlow.View<State, Action> {
override fun render(state: State) {
binding.normalCounter.text = state.normalCounter.toString()
binding.startedCounter.text = state.startedCounter.toString()
binding.resumedCounter.text = state.resumedCounter.toString()
/*
Note: this is not the normal way to use this library. This is just a contrived example to show the difference
between launch and launchWhenResumed (and other similar methods)
*/
val viewNormal = StateActionView(binding.normalCounter, "normal")
val mvFlowNormal = LifecycleMVFlow.create(lifecycleScope)
val viewStarted = StateActionView(binding.startedCounter, "started")
val mvFlowStarted = LifecycleMVFlow.create(lifecycleScope)
val viewResumed = StateActionView(binding.resumedCounter, "resumed")
val mvFlowResumed = LifecycleMVFlow.create(lifecycleScope)

binding.timersRunning.isChecked = state.timersRunning
val initialActions = listOf(Action.StartCounter)
lifecycleScope.launch {
mvFlowNormal.takeView(this, viewNormal, initialActions)
}
lifecycleScope.launchWhenStarted {
mvFlowStarted.takeView(this, viewStarted, initialActions)
}
lifecycleScope.launchWhenResumed {
mvFlowResumed.takeView(this, viewResumed, initialActions)
}

binding.delayedToggleTimers.isEnabled = state.delayedToggleWaiting.not()
binding.toggleProgressBar.visibility = if (state.delayedToggleWaiting) View.VISIBLE else View.INVISIBLE
}
binding.openDialog.setOnClickListener {
DummyDialogActivity.launch(this)
}
}

override fun actions(): Flow<Action> = callbackFlow {
binding.delayedToggleTimers.setOnClickListener {
offer(Action.ToggleTimersDelayed)
}
binding.openDialog.setOnClickListener {
offer(Action.OpenDialog)
}
binding.resetTimers.setOnClickListener {
offer(Action.ResetTimers)
}
binding.timersRunning.setOnCheckedChangeListener { _, checked ->
offer(Action.SetTimers(checked))
}
class StateActionView(private val textView: TextView, private val name: String) : MVFlow.View<State, Action> {
override fun render(state: State) {
Log.d("MYAPP", "lifecycle counter updated for $name with value ${state.counter}")
textView.text = state.counter.toString()
}

// these are not really actions coming from the view, but a proof of concept to allow to see the
// different ways you can use lifecycles to observe state updates how you want them.
//
// This sample highlighted some issues with the current API and we will address that soon.
val ticker = tickerBroadcastChannel()
lifecycleScope.launchWhenResumed {
ticker.openSubscription().consumeEach {
offer(Action.TickResumed)
}
}
override fun actions(): Flow<Action> = emptyFlow()
}

lifecycleScope.launch {
ticker.openSubscription().consumeEach {
offer(Action.TickNormal)
}
}
lifecycleScope.launchWhenStarted {
ticker.openSubscription().consumeEach {
offer(Action.TickStarted)
}
}
awaitClose()
}
}
override fun onStart() {
super.onStart()
logLifeCycle("onStart")
}

val mvFlow = LifecycleMVFlow.create(lifecycleScope)
lifecycleScope.launch {
mvFlow.takeView(this, view)
}
override fun onResume() {
super.onResume()
logLifeCycle("onResume")
}

lifecycleScope.launch {
mvFlow.observeEffects().filterIsInstance<Effect.OpenDialog>()
.collect {
DummyDialogActivity.launch(this@LifecycleActivity)
}
}
override fun onPause() {
logLifeCycle("onPause")
super.onPause()
}

override fun onStop() {
logLifeCycle("onStop")
super.onStop()
}

override fun onDestroy() {
logLifeCycle("onDestroy")
super.onDestroy()
}

@OptIn(ObsoleteCoroutinesApi::class)
private fun tickerBroadcastChannel(): BroadcastChannel<Unit> {
return ticker(
delayMillis = 1000,
initialDelayMillis = 0
).broadcast(capacity = Channel.CONFLATED)
private fun logLifeCycle(step: String) {
Log.d("MYAPP", "lifecycle step $step ${javaClass.simpleName}@${hashCode().toString(16)}")
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,92 +2,66 @@ package net.pedroloureiro.mvflow.samples.android.screens.lifecycle

import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import net.pedroloureiro.mvflow.HandlerWithEffects
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.broadcast
import kotlinx.coroutines.channels.ticker
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.transform
import net.pedroloureiro.mvflow.Handler
import net.pedroloureiro.mvflow.MVFlow
import net.pedroloureiro.mvflow.Reducer

object LifecycleMVFlow {
data class State(
val normalCounter: Int = 0,
val startedCounter: Int = 0,
val resumedCounter: Int = 0,
val timersRunning: Boolean = false,
val delayedToggleWaiting: Boolean = false
val counter: Int = 0
)

sealed class Action {
data class SetTimers(val running: Boolean) : Action()
object ToggleTimersDelayed : Action()
object OpenDialog : Action()
object ResetTimers : Action()
object TickNormal : Action()
object TickStarted : Action()
object TickResumed : Action()
object StartCounter : Action()
}

sealed class Mutation {
data class SetTimersRunning(val running: Boolean) : Mutation()
data class DelayedToggleWaiting(val waiting: Boolean) : Mutation()

/**
* A tick, meant to increment the timers
*/
object TickNormal : Mutation()
object TickStarted : Mutation()
object TickResumed : Mutation()

object ResetTimers : Mutation()
object ToggleTimers : Mutation()
object Tick : Mutation()
}

sealed class Effect {
object OpenDialog : Effect()
}

val handler: HandlerWithEffects<State, Action, Mutation, Effect> = { _, action, effects ->
when (action) {
Action.OpenDialog -> flow {
effects.send(Effect.OpenDialog)
// empty flow - we will listen externally to this and act there
}
/*
Note: this is not the normal way to use this library. This is just a contrived example to show the difference
between launch and launchWhenResumed (and other similar methods)
*/

is Action.SetTimers -> flowOf(Mutation.SetTimersRunning(action.running))
Action.ToggleTimersDelayed -> flow {
emit(Mutation.DelayedToggleWaiting(true))
delay(5000)
emit(Mutation.ToggleTimers)
emit(Mutation.DelayedToggleWaiting(false))
fun createHandler(): Handler<State, Action, Mutation> =
{ _, action ->
when (action) {
Action.StartCounter ->
tickerBroadcastChannel
.openSubscription()
.consumeAsFlow()
.transform {
emit(Mutation.Tick)
}
}

Action.ResetTimers -> flowOf(Mutation.ResetTimers)

Action.TickNormal -> flowOf(Mutation.TickNormal)
Action.TickStarted -> flowOf(Mutation.TickStarted)
Action.TickResumed -> flowOf(Mutation.TickResumed)
}
}

@OptIn(ObsoleteCoroutinesApi::class)
private val tickerBroadcastChannel =
ticker(
delayMillis = 1000,
initialDelayMillis = 0
).broadcast(Channel.CONFLATED)

val reducer: Reducer<State, Mutation> = { state, mutation ->
when (mutation) {
is Mutation.SetTimersRunning -> state.copy(timersRunning = mutation.running)
is Mutation.DelayedToggleWaiting -> state.copy(delayedToggleWaiting = mutation.waiting)
Mutation.TickNormal -> state.copy(normalCounter = state.normalCounter + 1)
Mutation.TickStarted -> state.copy(startedCounter = state.startedCounter + 1)
Mutation.TickResumed -> state.copy(resumedCounter = state.resumedCounter + 1)
Mutation.ResetTimers -> state.copy(normalCounter = 0, startedCounter = 0, resumedCounter = 0)
Mutation.ToggleTimers -> state.copy(timersRunning = state.timersRunning.not())
Mutation.Tick -> state.copy(counter = state.counter + 1)
}
}

fun create(coroutineScope: CoroutineScope) =
MVFlow(
State(),
handler,
createHandler(),
reducer,
coroutineScope,
defaultLogger = { Log.d("MVFLOW", it) }
defaultLogger = { Log.d("MYAPP", it) }
)
}
Loading

0 comments on commit edf5412

Please sign in to comment.