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

native link dagger setup #9465

Merged
merged 9 commits into from
Oct 21, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions link/api/link.api
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ public final class com/stripe/android/link/ComposableSingletons$LinkActivityKt {
public static field lambda-3 Lkotlin/jvm/functions/Function4;
public static field lambda-4 Lkotlin/jvm/functions/Function4;
public static field lambda-5 Lkotlin/jvm/functions/Function4;
public static field lambda-6 Lkotlin/jvm/functions/Function4;
public fun <init> ()V
public final fun getLambda-1$link_release ()Lkotlin/jvm/functions/Function4;
public final fun getLambda-2$link_release ()Lkotlin/jvm/functions/Function4;
public final fun getLambda-3$link_release ()Lkotlin/jvm/functions/Function4;
public final fun getLambda-4$link_release ()Lkotlin/jvm/functions/Function4;
public final fun getLambda-5$link_release ()Lkotlin/jvm/functions/Function4;
public final fun getLambda-6$link_release ()Lkotlin/jvm/functions/Function4;
}

public final class com/stripe/android/link/LinkActivityContract$Companion {
Expand Down Expand Up @@ -87,6 +89,14 @@ public final class com/stripe/android/link/LinkPaymentDetails$Saved$Creator : an
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/link/NativeLinkArgs$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/link/NativeLinkArgs;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lcom/stripe/android/link/NativeLinkArgs;
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/link/ui/ComposableSingletons$LinkButtonKt {
public static final field INSTANCE Lcom/stripe/android/link/ui/ComposableSingletons$LinkButtonKt;
public static field lambda-1 Lkotlin/jvm/functions/Function2;
Expand Down
8 changes: 5 additions & 3 deletions link/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
<application>
<activity
android:name="com.stripe.android.link.LinkActivity"
android:theme="@style/StripeLinkBaseTheme"
android:exported="false"
android:label="@string/stripe_link"
android:windowSoftInputMode="adjustResize"
android:autoRemoveFromRecents="true"
android:configChanges="orientation|keyboard|keyboardHidden|screenLayout|screenSize|smallestScreenSize"
android:launchMode="singleTop"
android:theme="@style/StripeTransparentTheme" />
android:configChanges="orientation|keyboard|keyboardHidden|screenLayout|screenSize|smallestScreenSize" />

<activity
android:name="com.stripe.android.link.LinkForegroundActivity"
Expand Down
59 changes: 49 additions & 10 deletions link/src/main/java/com/stripe/android/link/LinkActivity.kt
Original file line number Diff line number Diff line change
@@ -1,48 +1,71 @@
package com.stripe.android.link

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.core.os.bundleOf
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.stripe.android.core.Logger
import com.stripe.android.link.ui.cardedit.CardEditScreen
import com.stripe.android.link.ui.paymentmenthod.PaymentMethodScreen
import com.stripe.android.link.ui.signup.SignUpScreen
import com.stripe.android.link.ui.verification.VerificationScreen
import com.stripe.android.link.ui.wallet.WalletScreen
import com.stripe.android.ui.core.CircularProgressIndicator

internal class LinkActivity : AppCompatActivity() {
@VisibleForTesting
internal var viewModelFactory: ViewModelProvider.Factory = LinkActivityViewModel.Factory()
private val viewModel: LinkActivityViewModel by viewModels { viewModelFactory }
internal class LinkActivity : ComponentActivity() {
internal var viewModel: LinkActivityViewModel? = null
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you make this a lateinit variable? I think then you won't need to make the type nullable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it nullable because onDestroy will get called when we finish the activity and viewModel.unregisterActivity() will crash since it's not initialized

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh gotcha, thanks


@VisibleForTesting
internal lateinit var navController: NavHostController

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

try {
viewModel = ViewModelProvider(this, LinkActivityViewModel.factory())[LinkActivityViewModel::class.java]
} catch (e: NoArgsException) {
Logger.getInstance(BuildConfig.DEBUG).error("Failed to create LinkActivityViewModel", e)
setResult(Activity.RESULT_CANCELED)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this canceled instead of being a failure?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I followed the pattern here, but I'm happy to change it to a failure

finish()
}

setContent {
navController = rememberNavController()

LaunchedEffect(Unit) {
viewModel.navController = navController
viewModel.dismissWithResult = ::dismissWithResult
viewModel?.navController = navController
viewModel?.dismissWithResult = ::dismissWithResult
}

NavHost(
navController = navController,
startDestination = LinkScreen.SignUp.route
startDestination = LinkScreen.Loading.route
) {
composable(LinkScreen.Loading.route) {
Box(
modifier = Modifier
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}

composable(LinkScreen.SignUp.route) {
SignUpScreen()
}
Expand Down Expand Up @@ -79,6 +102,22 @@ internal class LinkActivity : AppCompatActivity() {

override fun onDestroy() {
super.onDestroy()
viewModel.unregisterActivity()
viewModel?.unregisterActivity()
}

companion object {
internal const val EXTRA_ARGS = "native_link_args"

internal fun createIntent(
context: Context,
args: NativeLinkArgs
): Intent {
return Intent(context, LinkActivity::class.java)
.putExtra(EXTRA_ARGS, args)
}

internal fun getArgs(savedStateHandle: SavedStateHandle): NativeLinkArgs? {
return savedStateHandle.get<NativeLinkArgs>(EXTRA_ARGS)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract
import androidx.annotation.RestrictTo
import com.stripe.android.PaymentConfiguration
import com.stripe.android.core.utils.FeatureFlags
import com.stripe.android.link.serialization.PopupPayload
import com.stripe.android.networking.StripeRepository
import javax.inject.Inject
Expand All @@ -15,6 +16,18 @@ class LinkActivityContract @Inject internal constructor(
) : ActivityResultContract<LinkActivityContract.Args, LinkActivityResult>() {

override fun createIntent(context: Context, input: Args): Intent {
return if (FeatureFlags.nativeLinkEnabled.isEnabled) {
nativeIntent(context, input)
} else {
webIntent(context, input)
}
}

override fun parseResult(resultCode: Int, intent: Intent?): LinkActivityResult {
return createLinkActivityResult(resultCode, intent)
}

private fun webIntent(context: Context, input: Args): Intent {
val paymentConfiguration = PaymentConfiguration.getInstance(context)
val payload = PopupPayload.create(
configuration = input.configuration,
Expand All @@ -26,13 +39,21 @@ class LinkActivityContract @Inject internal constructor(
return LinkForegroundActivity.createIntent(context, payload.toUrl())
}

override fun parseResult(resultCode: Int, intent: Intent?): LinkActivityResult {
return createLinkActivityResult(resultCode, intent)
private fun nativeIntent(context: Context, input: Args): Intent {
val paymentConfiguration = PaymentConfiguration.getInstance(context)
return LinkActivity.createIntent(
context = context,
args = NativeLinkArgs(
configuration = input.configuration,
stripeAccountId = paymentConfiguration.stripeAccountId,
publishableKey = paymentConfiguration.publishableKey
)
)
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class Args internal constructor(
internal val configuration: LinkConfiguration,
internal val configuration: LinkConfiguration
)

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
package com.stripe.android.link

import android.app.Application
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import androidx.navigation.NavHostController
import com.stripe.android.link.LinkActivity.Companion.getArgs
import com.stripe.android.link.injection.DaggerNativeLinkComponent
import com.stripe.android.link.injection.NativeLinkComponent
import javax.inject.Inject

internal class LinkActivityViewModel : ViewModel() {
internal class LinkActivityViewModel @Inject constructor(
val activityRetainedComponent: NativeLinkComponent
) : ViewModel() {
var navController: NavHostController? = null
var dismissWithResult: ((LinkActivityResult) -> Unit)? = null

Expand All @@ -27,9 +39,24 @@ internal class LinkActivityViewModel : ViewModel() {
dismissWithResult = null
}

internal class Factory : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return LinkActivityViewModel() as T
companion object {
fun factory(savedStateHandle: SavedStateHandle? = null): ViewModelProvider.Factory = viewModelFactory {
initializer {
val handle: SavedStateHandle = savedStateHandle ?: createSavedStateHandle()
val app = this[APPLICATION_KEY] as Application
val args: NativeLinkArgs = getArgs(handle) ?: throw NoArgsException()

DaggerNativeLinkComponent
.builder()
.configuration(args.configuration)
.publishableKeyProvider { args.publishableKey }
.stripeAccountIdProvider { args.stripeAccountId }
.context(app)
.build()
.viewModel
}
}
}
}

internal class NoArgsException : IllegalArgumentException("NativeLinkArgs not found")
1 change: 1 addition & 0 deletions link/src/main/java/com/stripe/android/link/LinkScreen.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.stripe.android.link

internal sealed class LinkScreen(val route: String) {
data object Loading : LinkScreen("loading")
data object Verification : LinkScreen("verification")
data object Wallet : LinkScreen("wallet")
data object PaymentMethod : LinkScreen("paymentMethod")
Expand Down
13 changes: 13 additions & 0 deletions link/src/main/java/com/stripe/android/link/NativeLinkArgs.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.stripe.android.link

import android.os.Parcelable
import androidx.annotation.RestrictTo
import kotlinx.parcelize.Parcelize

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Parcelize
internal data class NativeLinkArgs(
val configuration: LinkConfiguration,
val publishableKey: String,
val stripeAccountId: String?
) : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import com.stripe.android.link.BuildConfig
import com.stripe.android.link.LinkConfiguration
import com.stripe.android.link.LinkPaymentDetails
import com.stripe.android.link.analytics.LinkEventsReporter
import com.stripe.android.link.injection.LinkScope
import com.stripe.android.link.model.AccountStatus
import com.stripe.android.link.model.LinkAccount
import com.stripe.android.link.repositories.LinkRepository
Expand All @@ -26,7 +25,6 @@ import javax.inject.Inject
/**
* Manages the Link account for the current user, persisting it across app usages.
*/
@LinkScope
internal class DefaultLinkAccountManager @Inject constructor(
private val config: LinkConfiguration,
private val linkRepository: LinkRepository,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.stripe.android.link.injection

import android.content.Context
import com.stripe.android.core.Logger
import com.stripe.android.core.injection.PUBLISHABLE_KEY
import com.stripe.android.core.injection.STRIPE_ACCOUNT_ID
import com.stripe.android.link.LinkActivityViewModel
import com.stripe.android.link.LinkConfiguration
import com.stripe.android.link.account.LinkAccountManager
import com.stripe.android.link.analytics.LinkEventsReporter
import dagger.BindsInstance
import dagger.Component
import javax.inject.Named
import javax.inject.Scope

@Scope
@Retention(AnnotationRetention.RUNTIME)
internal annotation class NativeLinkScope

@NativeLinkScope
@Component(
modules = [
NativeLinkModule::class,
]
)
internal interface NativeLinkComponent {
val linkAccountManager: LinkAccountManager
val configuration: LinkConfiguration
val linkEventsReporter: LinkEventsReporter
val logger: Logger
val viewModel: LinkActivityViewModel

@Component.Builder
interface Builder {
@BindsInstance
fun configuration(configuration: LinkConfiguration): Builder

@BindsInstance
fun publishableKeyProvider(@Named(PUBLISHABLE_KEY) publishableKeyProvider: () -> String): Builder

@BindsInstance
fun stripeAccountIdProvider(@Named(STRIPE_ACCOUNT_ID) stripeAccountIdProvider: () -> String?): Builder

@BindsInstance
fun context(context: Context): Builder

fun build(): NativeLinkComponent
}
}
Loading
Loading