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

implement result API based on NavBackStackEntry #56

Merged
merged 5 commits into from
Mar 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion navigator/runtime-compose/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ dependencies {
implementation "androidx.compose.runtime:runtime:1.1.1"
implementation "androidx.lifecycle:lifecycle-runtime:2.4.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"
implementation "androidx.savedstate:savedstate-ktx:1.1.0"
implementation "androidx.navigation:navigation-compose:2.4.1"
implementation "com.google.accompanist:accompanist-navigation-material:0.23.1"

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.freeletics.mad.navigator.compose
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.os.Parcelable
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResultLauncher
Expand All @@ -11,9 +12,12 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Observer
import androidx.lifecycle.flowWithLifecycle
import androidx.navigation.NavController
import com.freeletics.mad.navigator.ActivityResultRequest
import com.freeletics.mad.navigator.NavEventNavigator
import com.freeletics.mad.navigator.NavigationResultRequest
import com.freeletics.mad.navigator.PermissionsResultRequest
import com.freeletics.mad.navigator.internal.RequestPermissionsContract
import com.freeletics.mad.navigator.internal.navigate
Expand All @@ -34,6 +38,10 @@ public fun NavigationSetup(navigator: NavEventNavigator) {
rememberResultLaunchers(it)
}

navigator.navigationResultRequests.forEach {
ResultEffect(it, controller)
}

val backDispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
DisposableEffect(lifecycleOwner, backDispatcher, navigator) {
backDispatcher.addCallback(lifecycleOwner, navigator.onBackPressedCallback)
Expand Down Expand Up @@ -77,3 +85,25 @@ private fun Context.findActivity(): Activity {
}
throw IllegalStateException("Permissions should be called in the context of an Activity")
}

@Composable
private fun <O : Parcelable> ResultEffect(
request: NavigationResultRequest<O>,
controller: NavController,
) {
DisposableEffect(request, controller) {
val liveData = controller.getBackStackEntry(request.key.destinationId).savedStateHandle
.getLiveData<Parcelable>(request.key.requestKey)

val observer = Observer<Parcelable> { result ->
@Suppress("UNCHECKED_CAST")
request.handleResult(result as O)
}

liveData.observeForever(observer)

onDispose {
liveData.removeObserver(observer)
}
}
}
12 changes: 1 addition & 11 deletions navigator/runtime-fragment/api/runtime-fragment.api
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
public abstract class com/freeletics/mad/navigator/fragment/FragmentNavEventNavigator : com/freeletics/mad/navigator/NavEventNavigator {
public fun <init> ()V
public final fun getResultEvents ()Lkotlinx/coroutines/flow/Flow;
public final fun navigateBackWithResult (Ljava/lang/String;Landroid/os/Parcelable;)V
public final fun registerForFragmentResult (Ljava/lang/String;)Lcom/freeletics/mad/navigator/fragment/FragmentResultRequest;
}

public final class com/freeletics/mad/navigator/fragment/FragmentResultRequest : com/freeletics/mad/navigator/ResultOwner {
}

public final class com/freeletics/mad/navigator/fragment/FragmentRouteKt {
public static final fun requireRoute (Landroidx/fragment/app/Fragment;)Lcom/freeletics/mad/navigator/BaseRoute;
}

public final class com/freeletics/mad/navigator/fragment/HandleNavigationKt {
public static final fun handleNavigation (Landroidx/fragment/app/Fragment;Lcom/freeletics/mad/navigator/fragment/FragmentNavEventNavigator;)V
public static final fun handleNavigation (Landroidx/fragment/app/Fragment;Lcom/freeletics/mad/navigator/NavEventNavigator;)V
}

public abstract interface class com/freeletics/mad/navigator/fragment/NavDestination {
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,37 +1,41 @@
package com.freeletics.mad.navigator.fragment

import android.annotation.SuppressLint
import android.app.Activity
import android.os.Bundle
import android.os.Parcelable
import androidx.activity.result.ActivityResultCaller
import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentResultOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController
import com.freeletics.mad.navigator.ActivityResultRequest
import com.freeletics.mad.navigator.NavEventNavigator
import com.freeletics.mad.navigator.NavigationResultRequest
import com.freeletics.mad.navigator.PermissionsResultRequest
import com.freeletics.mad.navigator.internal.RequestPermissionsContract
import com.freeletics.mad.navigator.internal.navigate
import kotlinx.coroutines.launch

/**
* Handles the [FragmentNavEventNavigator] events while the Fragment's lifecycle is at least
* Handles the [NavEventNavigator] events while the Fragment's lifecycle is at least
* started.
*/
public fun handleNavigation(fragment: Fragment, navigator: FragmentNavEventNavigator) {
public fun handleNavigation(fragment: Fragment, navigator: NavEventNavigator) {
val activityLaunchers = navigator.activityResultRequests.associateWith {
it.registerIn(fragment)
}
val permissionLaunchers = navigator.permissionsResultRequests.associateWith {
it.registerIn(fragment, fragment.requireActivity())
}

navigator.fragmentResultRequests.forEach {
it.registerIn(fragment.parentFragmentManager, fragment)
val controller = fragment.findNavController()
navigator.navigationResultRequests.forEach {
it.registerIn(controller, fragment)
}

val dispatcher = fragment.requireActivity().onBackPressedDispatcher
Expand All @@ -45,13 +49,6 @@ public fun handleNavigation(fragment: Fragment, navigator: FragmentNavEventNavig
}
}
}
lifecycle.coroutineScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
navigator.resultEvents.collect { event ->
navigate(fragment, event)
}
}
}
}

private fun <I, O> ActivityResultRequest<I, O>.registerIn(
Expand All @@ -69,22 +66,19 @@ private fun PermissionsResultRequest.registerIn(
}
}

@SuppressLint("VisibleForTests") // it's ok to use onResult internally
private fun <O> FragmentResultRequest<O>.registerIn(
fragmentResultOwner: FragmentResultOwner,
lifecycleOwner: LifecycleOwner
private fun <O : Parcelable> NavigationResultRequest<O>.registerIn(
controller: NavController,
lifecycleOwner: LifecycleOwner,
) {
fragmentResultOwner.setFragmentResultListener(requestKey, lifecycleOwner) { _, bundle ->
onResult(bundle.getParcelable(KEY_FRAGMENT_RESULT)!!)
}
}
val liveData = controller.getBackStackEntry(key.destinationId).savedStateHandle
.getLiveData<Parcelable>(key.requestKey)

private fun navigate(fragment: Fragment, event: FragmentResultEvent) {
val result = Bundle(1).apply {
putParcelable(KEY_FRAGMENT_RESULT, event.result)
val observer = Observer<Parcelable> { result ->
@Suppress("UNCHECKED_CAST")
handleResult(result as O)
}
fragment.parentFragmentManager.setFragmentResult(event.requestKey, result)
fragment.findNavController().popBackStack()

liveData.observe(lifecycleOwner, observer)
}

/**
Expand Down

This file was deleted.

30 changes: 29 additions & 1 deletion navigator/runtime/api/navigator-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ public class com/freeletics/mad/navigator/NavEventNavigator {
public fun <init> ()V
public final fun backPresses ()Lkotlinx/coroutines/flow/Flow;
public final fun backPresses (Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public final fun deliverNavigationResult (Lcom/freeletics/mad/navigator/NavigationResultRequest$Key;Landroid/os/Parcelable;)V
public final fun getActivityResultRequests ()Ljava/util/List;
public final fun getNavEvents ()Lkotlinx/coroutines/flow/Flow;
public final fun getNavigationResultRequests ()Ljava/util/List;
public final fun getOnBackPressedCallback ()Landroidx/activity/OnBackPressedCallback;
public final fun getPermissionsResultRequests ()Ljava/util/List;
public final fun navigateBack ()V
Expand All @@ -33,6 +35,33 @@ public abstract interface class com/freeletics/mad/navigator/NavRoot : com/freel
public abstract interface class com/freeletics/mad/navigator/NavRoute : com/freeletics/mad/navigator/BaseRoute {
}

public final class com/freeletics/mad/navigator/NavigationResultRequest : com/freeletics/mad/navigator/ResultOwner {
public final fun getKey ()Lcom/freeletics/mad/navigator/NavigationResultRequest$Key;
}

public final class com/freeletics/mad/navigator/NavigationResultRequest$Key : android/os/Parcelable {
public static final field CREATOR Lcom/freeletics/mad/navigator/NavigationResultRequest$Key$CREATOR;
public fun <init> (Landroid/os/Parcel;)V
public final fun component1 ()I
public final fun component2 ()Ljava/lang/String;
public final fun copy (ILjava/lang/String;)Lcom/freeletics/mad/navigator/NavigationResultRequest$Key;
public static synthetic fun copy$default (Lcom/freeletics/mad/navigator/NavigationResultRequest$Key;ILjava/lang/String;ILjava/lang/Object;)Lcom/freeletics/mad/navigator/NavigationResultRequest$Key;
public fun describeContents ()I
public fun equals (Ljava/lang/Object;)Z
public final fun getDestinationId ()I
public final fun getRequestKey ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
public fun writeToParcel (Landroid/os/Parcel;I)V
}

public final class com/freeletics/mad/navigator/NavigationResultRequest$Key$CREATOR : android/os/Parcelable$Creator {
public fun createFromParcel (Landroid/os/Parcel;)Lcom/freeletics/mad/navigator/NavigationResultRequest$Key;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public fun newArray (I)[Lcom/freeletics/mad/navigator/NavigationResultRequest$Key;
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/freeletics/mad/navigator/PermissionsResultRequest : com/freeletics/mad/navigator/ResultOwner {
}

Expand All @@ -45,7 +74,6 @@ public final class com/freeletics/mad/navigator/PermissionsResultRequest$Permiss
}

public abstract class com/freeletics/mad/navigator/ResultOwner {
public fun <init> ()V
public final fun getResults ()Lkotlinx/coroutines/flow/Flow;
}

Expand Down
1 change: 1 addition & 0 deletions navigator/runtime/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies {
implementation "androidx.core:core:1.7.0"
implementation "androidx.navigation:navigation-common:2.4.1"
implementation "androidx.navigation:navigation-runtime:2.4.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.4.1"

testImplementation "junit:junit:4.13.2"
testImplementation "com.google.truth:truth:1.1.3"
Expand Down