Skip to content

Commit

Permalink
First draft of RibLifecycle
Browse files Browse the repository at this point in the history
  • Loading branch information
psteiger committed May 24, 2023
1 parent 27d8eba commit 3104655
Show file tree
Hide file tree
Showing 17 changed files with 640 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ tasks.withType<KotlinCompile>().configureEach {
freeCompilerArgs.addAll(
"-Xexplicit-api=warning",
"-Xjvm-default=enable",
"-opt-in=kotlin.RequiresOptIn",
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/
@file:Suppress("invisible_reference", "invisible_member")
@file:OptIn(InternalRibsApi::class)

package com.uber.rib.core

Expand All @@ -24,7 +25,6 @@ import android.view.ViewGroup
import androidx.annotation.CallSuper
import com.uber.autodispose.lifecycle.CorrespondingEventsFunction
import com.uber.autodispose.lifecycle.LifecycleEndedException
import com.uber.autodispose.lifecycle.LifecycleNotStartedException
import com.uber.autodispose.lifecycle.LifecycleScopeProvider
import com.uber.rib.core.lifecycle.ActivityCallbackEvent
import com.uber.rib.core.lifecycle.ActivityCallbackEvent.Companion.create
Expand All @@ -37,6 +37,11 @@ import com.uber.rib.core.lifecycle.ActivityCallbackEvent.Companion.createWindowF
import com.uber.rib.core.lifecycle.ActivityLifecycleEvent
import com.uber.rib.core.lifecycle.ActivityLifecycleEvent.Companion.create
import com.uber.rib.core.lifecycle.ActivityLifecycleEvent.Companion.createOnCreateEvent
import com.uber.rib.core.lifecycle.RibLifecycle
import com.uber.rib.core.lifecycle.RibLifecycleOwner
import com.uber.rib.core.lifecycle.internal.InternalRibLifecycle
import com.uber.rib.core.lifecycle.internal.actualRibLifecycle
import com.uber.rib.core.lifecycle.internal.asScopeCompletable
import io.reactivex.CompletableSource
import io.reactivex.Observable
import kotlinx.coroutines.channels.BufferOverflow
Expand All @@ -48,19 +53,27 @@ import kotlinx.coroutines.rx2.asObservable
abstract class RibActivity :
CoreAppCompatActivity(),
ActivityStarter,
RibLifecycleOwner<ActivityLifecycleEvent>,
LifecycleScopeProvider<ActivityLifecycleEvent>,
RxActivityEvents {
private var router: ViewRouter<*, *>? = null

private val _lifecycleFlow =
MutableSharedFlow<ActivityLifecycleEvent>(1, 0, BufferOverflow.DROP_OLDEST)
private val _ribLifecycle = InternalRibLifecycle(LIFECYCLE_RANGE)
override val ribLifecycle: RibLifecycle<ActivityLifecycleEvent>
get() = _ribLifecycle

open val lifecycleFlow: SharedFlow<ActivityLifecycleEvent>
get() = _lifecycleFlow
@Volatile private var mockedRibLifecycleRef: RibLifecycle<ActivityLifecycleEvent>? = null

@Deprecated("This field should never be used on real code", level = DeprecationLevel.ERROR)
final override val actualRibLifecycle: RibLifecycle<ActivityLifecycleEvent>
get() = actualRibLifecycle(::mockedRibLifecycleRef, LIFECYCLE_RANGE)

@Volatile private var _lifecycleObservable: Observable<ActivityLifecycleEvent>? = null

@Suppress("DEPRECATION_ERROR")
private val lifecycleObservable
get() = ::_lifecycleObservable.setIfNullAndGet { lifecycleFlow.asObservable() }
get() =
::_lifecycleObservable.setIfNullAndGet { actualRibLifecycle.lifecycleFlow.asObservable() }

private val _callbacksFlow =
MutableSharedFlow<ActivityCallbackEvent>(0, 1, BufferOverflow.DROP_OLDEST)
Expand All @@ -84,14 +97,14 @@ abstract class RibActivity :
lifecycleFlow.replayCache.lastOrNull()

final override fun requestScope(): CompletableSource =
lifecycleFlow.asScopeCompletable(lifecycleRange)
lifecycleFlow.asScopeCompletable(LIFECYCLE_RANGE)

@Initializer
@CallSuper
override fun onCreate(savedInstanceState: android.os.Bundle?) {
super.onCreate(savedInstanceState)
val rootViewGroup = findViewById<ViewGroup>(android.R.id.content)
_lifecycleFlow.tryEmit(createOnCreateEvent(savedInstanceState))
_ribLifecycle.lifecycleFlow.tryEmit(createOnCreateEvent(savedInstanceState))
val wrappedBundle: Bundle? =
if (savedInstanceState != null) Bundle(savedInstanceState) else null
router = createRouter(rootViewGroup)
Expand All @@ -113,13 +126,13 @@ abstract class RibActivity :
@CallSuper
override fun onStart() {
super.onStart()
_lifecycleFlow.tryEmit(create(ActivityLifecycleEvent.Type.START))
_ribLifecycle.lifecycleFlow.tryEmit(create(ActivityLifecycleEvent.Type.START))
}

@CallSuper
override fun onResume() {
super.onResume()
_lifecycleFlow.tryEmit(create(ActivityLifecycleEvent.Type.RESUME))
_ribLifecycle.lifecycleFlow.tryEmit(create(ActivityLifecycleEvent.Type.RESUME))
}

@CallSuper
Expand All @@ -136,19 +149,19 @@ abstract class RibActivity :

@CallSuper
override fun onPause() {
_lifecycleFlow.tryEmit(create(ActivityLifecycleEvent.Type.PAUSE))
_ribLifecycle.lifecycleFlow.tryEmit(create(ActivityLifecycleEvent.Type.PAUSE))
super.onPause()
}

@CallSuper
override fun onStop() {
_lifecycleFlow.tryEmit(create(ActivityLifecycleEvent.Type.STOP))
_ribLifecycle.lifecycleFlow.tryEmit(create(ActivityLifecycleEvent.Type.STOP))
super.onStop()
}

@CallSuper
override fun onDestroy() {
_lifecycleFlow.tryEmit(create(ActivityLifecycleEvent.Type.DESTROY))
_ribLifecycle.lifecycleFlow.tryEmit(create(ActivityLifecycleEvent.Type.DESTROY))
router?.let {
it.dispatchDetach()
RibEvents.getInstance().emitEvent(RibEventType.DETACHED, it, null)
Expand Down Expand Up @@ -196,7 +209,7 @@ abstract class RibActivity :
}

override fun onUserLeaveHint() {
_lifecycleFlow.tryEmit(create(ActivityLifecycleEvent.Type.USER_LEAVING))
_ribLifecycle.lifecycleFlow.tryEmit(create(ActivityLifecycleEvent.Type.USER_LEAVING))
super.onUserLeaveHint()
}

Expand Down Expand Up @@ -251,12 +264,8 @@ abstract class RibActivity :
)
}
}
}
}

private val <T : Comparable<T>> LifecycleScopeProvider<T>.lifecycleRange: ClosedRange<T>
get() {
val lastEmittedEvent = peekLifecycle() ?: throw LifecycleNotStartedException()
val finishingEvent = correspondingEvents().apply(lastEmittedEvent)
return lastEmittedEvent..finishingEvent
private val LIFECYCLE_RANGE =
createOnCreateEvent(null)..create(ActivityLifecycleEvent.Type.DESTROY)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2023. Uber Technologies
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("MatchingDeclarationName", "ktlint:filename")

package com.uber.rib.core

@Retention(value = AnnotationRetention.BINARY)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.PROPERTY,
)
@RequiresOptIn(
level = RequiresOptIn.Level.ERROR,
message = "This is an internal RIBs API that should not be used by the public.",
)
public annotation class InternalRibsApi
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,17 @@ import androidx.annotation.VisibleForTesting
import com.uber.autodispose.lifecycle.CorrespondingEventsFunction
import com.uber.autodispose.lifecycle.LifecycleEndedException
import com.uber.rib.core.lifecycle.InteractorEvent
import com.uber.rib.core.lifecycle.RibLifecycle
import com.uber.rib.core.lifecycle.coroutineScope as lifecycleCoroutineScope
import com.uber.rib.core.lifecycle.internal.InternalRibLifecycle
import com.uber.rib.core.lifecycle.internal.actualRibLifecycle
import com.uber.rib.core.lifecycle.internal.asScopeCompletable
import io.reactivex.CompletableSource
import io.reactivex.Observable
import javax.inject.Inject
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.rx2.asObservable

/**
Expand All @@ -36,17 +39,11 @@ import kotlinx.coroutines.rx2.asObservable
* @param <P> the type of [Presenter].
* @param <R> the type of [Router].
*/
@OptIn(InternalRibsApi::class)
public abstract class Interactor<P : Any, R : Router<*>>() : InteractorType {

@Inject public lateinit var injectedPresenter: P
internal var actualPresenter: P? = null
private val _lifecycleFlow = MutableSharedFlow<InteractorEvent>(1, 0, BufferOverflow.DROP_OLDEST)
public open val lifecycleFlow: SharedFlow<InteractorEvent>
get() = _lifecycleFlow

@Volatile private var _lifecycleObservable: Observable<InteractorEvent>? = null
private val lifecycleObservable
get() = ::_lifecycleObservable.setIfNullAndGet { lifecycleFlow.asObservable() }

private val routerDelegate = InitOnceProperty<R>()

/** @return the router for this interactor. */
Expand All @@ -57,22 +54,43 @@ public abstract class Interactor<P : Any, R : Router<*>>() : InteractorType {
this.actualPresenter = presenter
}

private val _ribLifecycle = InternalRibLifecycle(lifecycleRange)
override val ribLifecycle: RibLifecycle<InteractorEvent>
get() = _ribLifecycle

// For retro compatibility

@Volatile private var mockedRibLifecycleRef: RibLifecycle<InteractorEvent>? = null

@Deprecated("This field should never be used on real code", level = DeprecationLevel.ERROR)
final override val actualRibLifecycle: RibLifecycle<InteractorEvent>
get() = actualRibLifecycle(::mockedRibLifecycleRef, lifecycleRange)

@Volatile private var _lifecycleObservable: Observable<InteractorEvent>? = null

@Suppress("DEPRECATION_ERROR")
private val lifecycleObservable
get() =
::_lifecycleObservable.setIfNullAndGet { actualRibLifecycle.lifecycleFlow.asObservable() }

// ---- LifecycleScopeProvider overrides ---- //

final override fun lifecycle(): Observable<InteractorEvent> = lifecycleObservable

final override fun correspondingEvents(): CorrespondingEventsFunction<InteractorEvent> =
LIFECYCLE_MAP_FUNCTION

final override fun peekLifecycle(): InteractorEvent? = lifecycleFlow.replayCache.lastOrNull()
@Suppress("DEPRECATION_ERROR")
final override fun peekLifecycle(): InteractorEvent? =
actualRibLifecycle.lifecycleFlow.replayCache.lastOrNull()

@Suppress("DEPRECATION_ERROR")
final override fun requestScope(): CompletableSource =
lifecycleFlow.asScopeCompletable(lifecycleRange)
actualRibLifecycle.lifecycleFlow.asScopeCompletable(lifecycleRange)

// ---- InteractorType overrides ---- //

override fun isAttached(): Boolean =
_lifecycleFlow.replayCache.lastOrNull() == InteractorEvent.ACTIVE
ribLifecycle.lifecycleFlow.replayCache.lastOrNull() == InteractorEvent.ACTIVE

override fun handleBackPress(): Boolean = false

Expand Down Expand Up @@ -101,15 +119,15 @@ public abstract class Interactor<P : Any, R : Router<*>>() : InteractorType {
protected open fun onSaveInstanceState(outState: Bundle) {}

public open fun dispatchAttach(savedInstanceState: Bundle?) {
_lifecycleFlow.tryEmit(InteractorEvent.ACTIVE)
_ribLifecycle.lifecycleFlow.tryEmit(InteractorEvent.ACTIVE)
(getPresenter() as? Presenter)?.dispatchLoad()
didBecomeActive(savedInstanceState)
}

public open fun dispatchDetach(): P {
(getPresenter() as? Presenter)?.dispatchUnload()
willResignActive()
_lifecycleFlow.tryEmit(InteractorEvent.INACTIVE)
_ribLifecycle.lifecycleFlow.tryEmit(InteractorEvent.INACTIVE)
return getPresenter()
}

Expand Down Expand Up @@ -161,8 +179,7 @@ public abstract class Interactor<P : Any, R : Router<*>>() : InteractorType {
}

public companion object {
@get:JvmSynthetic internal val lifecycleRange = InteractorEvent.ACTIVE..InteractorEvent.INACTIVE

private val lifecycleRange = InteractorEvent.ACTIVE..InteractorEvent.INACTIVE
private val LIFECYCLE_MAP_FUNCTION =
CorrespondingEventsFunction { interactorEvent: InteractorEvent ->
when (interactorEvent) {
Expand All @@ -172,3 +189,9 @@ public abstract class Interactor<P : Any, R : Router<*>>() : InteractorType {
}
}
}

@Deprecated(
"Replace the 'com.uber.core.coroutineScope' import with 'com.uber.core.lifecycle.coroutineScope'",
)
public val Interactor<*, *>.coroutineScope: CoroutineScope
get() = this.lifecycleCoroutineScope
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ package com.uber.rib.core

import com.uber.autodispose.lifecycle.LifecycleScopeProvider
import com.uber.rib.core.lifecycle.InteractorEvent
import com.uber.rib.core.lifecycle.RibLifecycleOwner

/**
* An interface used as the upper bound of the generic used by [Router]s to avoid cyclic generic
* types
*/
public interface InteractorType : LifecycleScopeProvider<InteractorEvent> {
public interface InteractorType :
RibLifecycleOwner<InteractorEvent>, LifecycleScopeProvider<InteractorEvent> {
/** @return `true` if the controller is attached, `false` if not. */
public fun isAttached(): Boolean

Expand Down
Loading

0 comments on commit 3104655

Please sign in to comment.