Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First draft of RibLifecycle #579

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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