Skip to content

Commit

Permalink
Change flow lifecycle to listener and move to view (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
itaihanski authored Nov 11, 2024
1 parent 68b54f5 commit eb1e0ba
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 67 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,7 @@ Read the class documentation for a detailed explanation. The flow needs to resid
some form, and to start it, call the `run()` function

```kotlin
val descopeFlow = DescopeFlow(Uri.parse("<URL_FOR_FLOW_IN_SETUP_#1>"))
descopeFlow.lifecycle = object : DescopeFlow.LifeCycle {
descopeFlowView.listener = object : DescopeFlowView.Listener {
override fun onReady() {
// present the flow view via animation, or however you see fit
}
Expand All @@ -266,12 +265,13 @@ descopeFlow.lifecycle = object : DescopeFlow.LifeCycle {
// handle any errors here
}

override fun onNavigation(uri: Uri): Flow.NavigationStrategy {
override fun onNavigation(uri: Uri): DescopeFlowView.NavigationStrategy {
// manage navigation event by deciding whether to open the URI
// in a custom tab (default behavior), inline, or do nothing.
}
}

val descopeFlow = DescopeFlow(Uri.parse("<URL_FOR_FLOW_IN_SETUP_#1>"))
// set the OAuth provider ID that is configured to "sign in with Google"
descopeFlow.oauthProvider = OAuthProvider.Google
// set the oauth redirect URI to use your app's deep link
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import android.webkit.WebViewClient
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.descope.Descope
import com.descope.android.DescopeFlow.NavigationStrategy.DoNothing
import com.descope.android.DescopeFlow.NavigationStrategy.Inline
import com.descope.android.DescopeFlow.NavigationStrategy.OpenBrowser
import com.descope.android.DescopeFlowView.NavigationStrategy.DoNothing
import com.descope.android.DescopeFlowView.NavigationStrategy.Inline
import com.descope.android.DescopeFlowView.NavigationStrategy.OpenBrowser
import com.descope.internal.http.JwtServerResponse
import com.descope.internal.http.REFRESH_COOKIE_NAME
import com.descope.internal.http.SESSION_COOKIE_NAME
Expand All @@ -40,6 +40,8 @@ import java.net.HttpCookie
@SuppressLint("SetJavaScriptEnabled")
internal class DescopeFlowCoordinator(private val webView: WebView) {

internal var listener: DescopeFlowView.Listener? = null

private lateinit var flow: DescopeFlow
private var state: State = State.Initial
private val handler: Handler = Handler(Looper.getMainLooper())
Expand All @@ -65,7 +67,7 @@ internal class DescopeFlowCoordinator(private val webView: WebView) {
state = State.Ready
logger?.log(Info, "Flow is ready")
handler.post {
flow.lifecycle?.onReady()
listener?.onReady()
}
}

Expand All @@ -84,7 +86,7 @@ internal class DescopeFlowCoordinator(private val webView: WebView) {
jwtServerResponse.sessionJwt = jwtServerResponse.sessionJwt ?: findJwtInCookies(cookieString, projectId = projectId, name = SESSION_COOKIE_NAME)
jwtServerResponse.refreshJwt = jwtServerResponse.refreshJwt ?: findJwtInCookies(cookieString, projectId = projectId, name = REFRESH_COOKIE_NAME)
handler.post {
flow.lifecycle?.onSuccess(jwtServerResponse.convert())
listener?.onSuccess(jwtServerResponse.convert())
}
}

Expand All @@ -97,7 +99,7 @@ internal class DescopeFlowCoordinator(private val webView: WebView) {
state = State.Failed
logger?.log(Error, "Flow finished with an exception", error)
handler.post {
flow.lifecycle?.onError(DescopeException.flowFailed.with(desc = error))
listener?.onError(DescopeException.flowFailed.with(desc = error))
}
}

Expand Down Expand Up @@ -169,7 +171,7 @@ internal class DescopeFlowCoordinator(private val webView: WebView) {
val uri = request?.url ?: return false
if (request.isRedirect) return false
logger?.log(Info, "Flow attempting to navigate to a URL", uri)
return when (flow.lifecycle?.onNavigation(uri) ?: OpenBrowser) {
return when (listener?.onNavigation(uri) ?: OpenBrowser) {
Inline -> false
DoNothing -> true
OpenBrowser -> {
Expand Down
122 changes: 65 additions & 57 deletions descopesdk/src/main/java/com/descope/android/DescopeFlowView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ import com.descope.types.OAuthProvider
*/
data class DescopeFlow(val uri: Uri) {

/** The [Lifecycle] property is called according to the Flow's state */
var lifecycle: Lifecycle? = null

/** Provide an instance of `DescopeSdk` if a custom instance was initialized. Leave `null` to use [Descope]*/
var sdk: DescopeSdk? = null

Expand Down Expand Up @@ -65,70 +62,20 @@ data class DescopeFlow(val uri: Uri) {
*/
var presentation: Presentation? = null

/**
* The [Lifecycle] interface is used to communicate Flow life cycle events back to the caller.
*/
interface Lifecycle {
/**
* Called when a flow is fully loaded and ready to be displayed
*/
fun onReady()

/**
* Called when a flow has completed successfully. Typically create a [DescopeSession]
* and manage it using [Descope.sessionManager]
*
* @param response The successful authentication response
*/
fun onSuccess(response: AuthenticationResponse)

/**
* Called when a flow has encountered an error.
*
* Typically a flow will not to be restarted at this point.
*
* @param exception What caused the error
*/
fun onError(exception: DescopeException)

/**
* Called when the flow attempts to navigate to a different URL.
* The [DescopeFlowView] will act upon the response from this function.
* It will either open an external custom tab, if [NavigationStrategy.OpenBrowser] is returned,
* allow the navigation to happen [NavigationStrategy.Inline] or allow the caller to
* override the navigation altogether if [NavigationStrategy.DoNothing] is returned.
*
* @param uri The URI to navigate to
* @return The [NavigationStrategy] to act upon
*/
fun onNavigation(uri: Uri): NavigationStrategy = NavigationStrategy.OpenBrowser
}

/**
* Customize the flow's presentation by implementing the [Presentation] interface.
*/
interface Presentation {
/**
* Provide your own [CustomTabsIntent] that will be used when a custom tab
* is required, e.g. when performing web-based OAuth authentication,
* or when [NavigationStrategy.OpenBrowser] is returned for navigation events,
* or when [DescopeFlowView.NavigationStrategy.OpenBrowser] is returned for navigation events,
* which is also the default behavior.
* @param context The context the [DescopeFlowView] resides inside.
* @return A [CustomTabsIntent]. Returning `null` will use the default custom tab intent.
*/
fun createCustomTabsIntent(context: Context): CustomTabsIntent?
}

/**
* Returned from a [Lifecycle.onNavigation] call, and determines
* the how to handle navigation event. This is useful to override
* URL opening for certain use-cases or provide you're own implementation.
*/
enum class NavigationStrategy {
OpenBrowser,
Inline,
DoNothing,
}
}

/**
Expand Down Expand Up @@ -176,8 +123,7 @@ data class DescopeFlow(val uri: Uri) {
* Read the [DescopeFlow] documentation for a detailed,
* explanation of the available required and optional configurations.
*
* val descopeFlow = DescopeFlow(Uri.parse("my-flow-url"))
* descopeFlow.lifecycle = object : DescopeFlow.LifeCycle {
* descopeFlowView.listener = object : DescopeFlowView.Listener {
* override fun onReady() {
* // present the flow view via animation, or however you see fit
* }
Expand All @@ -195,12 +141,13 @@ data class DescopeFlow(val uri: Uri) {
* // handle any errors here
* }
*
* override fun onNavigation(uri: Uri): Flow.NavigationStrategy {
* override fun onNavigation(uri: Uri): DescopeFlowView.NavigationStrategy {
* // manage navigation event by deciding whether to open the URI
* // in a custom tab (default behavior), inline, or do nothing.
* }
* }
*
* val descopeFlow = DescopeFlow(Uri.parse("https://example.com"))
* // set the OAuth provider ID that is configured to "sign in with Google"
* descopeFlow.oauthProvider = OAuthProvider.Google
* // set the oauth redirect URI to use your app's deep link
Expand All @@ -214,6 +161,15 @@ data class DescopeFlow(val uri: Uri) {
*/
class DescopeFlowView : ViewGroup {

/** The [Listener] property is called according to the Flow's state */
var listener: Listener?
get() = if (this::flowCoordinator.isInitialized) flowCoordinator.listener else null
set(value) {
if (this::flowCoordinator.isInitialized) {
flowCoordinator.listener = value
}
}

private lateinit var flowCoordinator: DescopeFlowCoordinator

constructor(context: Context) : super(context, null, 0) {
Expand Down Expand Up @@ -269,4 +225,56 @@ class DescopeFlowView : ViewGroup {
child.layout(0, 0, width, height);
}
}

// Helper Classes

/**
* The [Listener] interface is used to communicate Flow lifecycle events back to the caller.
*/
interface Listener {
/**
* Called when a flow is fully loaded and ready to be displayed
*/
fun onReady()

/**
* Called when a flow has completed successfully. Typically create a [DescopeSession]
* and manage it using [Descope.sessionManager]
*
* @param response The successful authentication response
*/
fun onSuccess(response: AuthenticationResponse)

/**
* Called when a flow has encountered an error.
*
* Typically a flow will not to be restarted at this point.
*
* @param exception What caused the error
*/
fun onError(exception: DescopeException)

/**
* Called when the flow attempts to navigate to a different URL.
* The [DescopeFlowView] will act upon the response from this function.
* It will either open an external custom tab, if [NavigationStrategy.OpenBrowser] is returned,
* allow the navigation to happen [NavigationStrategy.Inline] or allow the caller to
* override the navigation altogether if [NavigationStrategy.DoNothing] is returned.
*
* @param uri The URI to navigate to
* @return The [NavigationStrategy] to act upon
*/
fun onNavigation(uri: Uri): NavigationStrategy = NavigationStrategy.OpenBrowser
}

/**
* Returned from a [Listener.onNavigation] call, and determines
* the how to handle navigation event. This is useful to override
* URL opening for certain use-cases or provide you're own implementation.
*/
enum class NavigationStrategy {
OpenBrowser,
Inline,
DoNothing,
}
}

0 comments on commit eb1e0ba

Please sign in to comment.