Skip to content

Commit 8c3e5e5

Browse files
authored
Compose Firebase Dynamic Links (#1477)
1 parent b634e62 commit 8c3e5e5

12 files changed

Lines changed: 623 additions & 127 deletions

File tree

dynamiclinks/app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ dependencies {
8989
implementation "androidx.compose.material:material:$compose_version"
9090
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
9191
implementation 'androidx.activity:activity-compose:1.5.1'
92+
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'
9293

9394
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
9495
androidTestImplementation 'androidx.test:rules:1.4.0'

dynamiclinks/app/src/main/AndroidManifest.xml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
<activity android:name=".java.MainActivity"
2121
android:exported="true">
22-
<!-- [START link_intent_filter] -->
2322
<intent-filter>
2423
<action android:name="android.intent.action.VIEW"/>
2524
<category android:name="android.intent.category.DEFAULT"/>
@@ -28,7 +27,6 @@
2827
android:host="example.com"
2928
android:scheme="https"/>
3029
</intent-filter>
31-
<!-- [END link_intent_filter] -->
3230
</activity>
3331

3432
<activity android:name=".kotlin.MainActivity"
@@ -42,6 +40,18 @@
4240
android:scheme="https"/>
4341
</intent-filter>
4442
</activity>
43+
44+
<activity android:name=".kotlin.MainComposeActivity"
45+
android:exported="true">
46+
<intent-filter>
47+
<action android:name="android.intent.action.VIEW"/>
48+
<category android:name="android.intent.category.DEFAULT"/>
49+
<category android:name="android.intent.category.BROWSABLE"/>
50+
<data
51+
android:host="kotlin.example.com"
52+
android:scheme="https"/>
53+
</intent-filter>
54+
</activity>
4555
</application>
4656

4757
</manifest>

dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/EntryChoiceActivity.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ class EntryChoiceActivity : BaseEntryChoiceActivity() {
1515
Choice(
1616
"Kotlin",
1717
"Run the Firebase Dynamic Links quickstart written in Kotlin.",
18-
Intent(this, com.google.firebase.quickstart.deeplinks.kotlin.MainActivity::class.java))
18+
Intent(this, com.google.firebase.quickstart.deeplinks.kotlin.MainActivity::class.java)),
19+
Choice(
20+
"Compose",
21+
"Run the Firebase Dynamic Links quickstart written in Compose.",
22+
Intent(this, com.google.firebase.quickstart.deeplinks.kotlin.MainComposeActivity::class.java))
1923
)
2024
}
2125
}

dynamiclinks/app/src/main/java/com/google/firebase/quickstart/deeplinks/java/MainActivity.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,8 @@ public class MainActivity extends AppCompatActivity {
4646
private static final String TAG = "MainActivity";
4747
private static final String DEEP_LINK_URL = "https://example.com/deeplinks";
4848

49-
// [START on_create]
5049
@Override
5150
protected void onCreate(Bundle savedInstanceState) {
52-
// [START_EXCLUDE]
5351
super.onCreate(savedInstanceState);
5452
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
5553
setContentView(binding.getRoot());
@@ -89,9 +87,7 @@ public void onClick(View v) {
8987
buildShortLinkFromParams(deepLink, 0);
9088
}
9189
});
92-
// [END_EXCLUDE]
9390

94-
// [START get_deep_link]
9591
FirebaseDynamicLinks.getInstance()
9692
.getDynamicLink(getIntent())
9793
.addOnSuccessListener(this, new OnSuccessListener<PendingDynamicLinkData>() {
@@ -109,7 +105,6 @@ public void onSuccess(PendingDynamicLinkData pendingDynamicLinkData) {
109105
// account.
110106
// ...
111107

112-
// [START_EXCLUDE]
113108
// Display deep link in the UI
114109
if (deepLink != null) {
115110
Snackbar.make(findViewById(android.R.id.content),
@@ -119,7 +114,6 @@ public void onSuccess(PendingDynamicLinkData pendingDynamicLinkData) {
119114
} else {
120115
Log.d(TAG, "getDynamicLink: no link found");
121116
}
122-
// [END_EXCLUDE]
123117
}
124118
})
125119
.addOnFailureListener(this, new OnFailureListener() {
@@ -128,9 +122,7 @@ public void onFailure(@NonNull Exception e) {
128122
Log.w(TAG, "getDynamicLink:onFailure", e);
129123
}
130124
});
131-
// [END get_deep_link]
132125
}
133-
// [END on_create]
134126

135127
/**
136128
* Build a Firebase Dynamic Link.
@@ -152,7 +144,6 @@ public Uri buildDeepLink(@NonNull Uri deepLink, int minVersion) {
152144
// * URI prefix (required)
153145
// * Android Parameters (required)
154146
// * Deep link
155-
// [START build_dynamic_link]
156147
DynamicLink.Builder builder = FirebaseDynamicLinks.getInstance()
157148
.createDynamicLink()
158149
.setDomainUriPrefix(uriPrefix)
@@ -163,7 +154,6 @@ public Uri buildDeepLink(@NonNull Uri deepLink, int minVersion) {
163154

164155
// Build the dynamic link
165156
DynamicLink link = builder.buildDynamicLink();
166-
// [END build_dynamic_link]
167157

168158
// Return the dynamic link as a URI
169159
return link.getUri();
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package com.google.firebase.quickstart.deeplinks.kotlin
2+
3+
import android.content.Intent
4+
import android.net.Uri
5+
import android.util.Log
6+
import androidx.lifecycle.ViewModel
7+
import androidx.lifecycle.ViewModelProvider
8+
import androidx.lifecycle.viewModelScope
9+
import androidx.lifecycle.viewmodel.CreationExtras
10+
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks
11+
import com.google.firebase.dynamiclinks.PendingDynamicLinkData
12+
import com.google.firebase.dynamiclinks.ktx.androidParameters
13+
import com.google.firebase.dynamiclinks.ktx.dynamicLink
14+
import com.google.firebase.dynamiclinks.ktx.dynamicLinks
15+
import com.google.firebase.dynamiclinks.ktx.shortLinkAsync
16+
import com.google.firebase.ktx.Firebase
17+
import kotlinx.coroutines.flow.MutableStateFlow
18+
import kotlinx.coroutines.flow.StateFlow
19+
import kotlinx.coroutines.launch
20+
import kotlinx.coroutines.tasks.await
21+
22+
class DynamicLinksViewModel(
23+
private val dynamicLinks: FirebaseDynamicLinks
24+
): ViewModel() {
25+
26+
private val _deepLink = MutableStateFlow("")
27+
val deepLink: StateFlow<String> = _deepLink
28+
29+
private val _shortLink = MutableStateFlow("")
30+
val shortLink: StateFlow<String> = _shortLink
31+
32+
private val _validUriPrefix = MutableStateFlow(true)
33+
val validUriPrefix: StateFlow<Boolean> = _validUriPrefix
34+
35+
fun getDynamicLink(intent: Intent) {
36+
viewModelScope.launch {
37+
try {
38+
val pendingDynamicLinkData: PendingDynamicLinkData = dynamicLinks
39+
.getDynamicLink(intent)
40+
.await()
41+
42+
val deepLink: Uri? = pendingDynamicLinkData.link
43+
44+
// Handle the deep link. For example, open the linked
45+
// content, or apply promotional credit to the user's
46+
// account.
47+
// ...
48+
49+
// Display deep link in the UI
50+
if (deepLink != null) {
51+
_deepLink.value = deepLink.toString()
52+
} else {
53+
Log.d(TAG, "getDynamicLink: no link found")
54+
}
55+
} catch (e: Exception) {
56+
Log.w(TAG, "getDynamicLink:onFailure", e)
57+
}
58+
}
59+
}
60+
61+
62+
/**
63+
* Build a Firebase Dynamic Link.
64+
* https://firebase.google.com/docs/dynamic-links/android/create#create-a-dynamic-link-from-parameters
65+
*
66+
* @param deepLink the deep link your app will open. This link must be a valid URL and use the
67+
* HTTP or HTTPS scheme.
68+
* @param minVersion the `versionCode` of the minimum version of your app that can open
69+
* the deep link. If the installed app is an older version, the user is taken
70+
* to the Play store to upgrade the app. Pass 0 if you do not
71+
* require a minimum version.
72+
* @return a [Uri] representing a properly formed deep link.
73+
*/
74+
// @VisibleForTesting
75+
fun buildDeepLink(uriPrefix: String, deepLink: Uri, minVersion: Int): Uri {
76+
// Set dynamic link parameters:
77+
// * URI prefix (required)
78+
// * Android Parameters (required)
79+
// * Deep link
80+
// Build the dynamic link
81+
val link = Firebase.dynamicLinks.dynamicLink {
82+
domainUriPrefix = uriPrefix
83+
androidParameters {
84+
minimumVersion = minVersion
85+
}
86+
link = deepLink
87+
}
88+
89+
// Return the dynamic link as a URI
90+
return link.uri
91+
}
92+
93+
// @VisibleForTesting
94+
fun buildShortLinkFromParams(uriPrefix: String, deepLink: Uri, minVersion: Int) {
95+
// Set dynamic link parameters:
96+
// * URI prefix (required)
97+
// * Android Parameters (required)
98+
// * Deep link
99+
100+
try {
101+
viewModelScope.launch {
102+
val shortDynamicLinks = Firebase.dynamicLinks.shortLinkAsync {
103+
link = deepLink
104+
domainUriPrefix = uriPrefix
105+
androidParameters {
106+
minimumVersion = minVersion
107+
}
108+
}.await()
109+
110+
val shortLinks = shortDynamicLinks.shortLink
111+
// val flowChartLink = shortDynamicLinks.previewLink
112+
113+
_shortLink.value = shortLinks.toString()
114+
}
115+
} catch (e: Exception) {
116+
Log.e(TAG, e.toString())
117+
}
118+
}
119+
120+
fun validateAppCode(uriPrefix: String) {
121+
if (uriPrefix.contains("YOUR_APP")) {
122+
_validUriPrefix.value = false
123+
}
124+
}
125+
126+
companion object {
127+
const val TAG = "DynamicLinksViewModel"
128+
129+
// Used to inject this ViewModel's dependencies
130+
// See also: https://developer.android.com/topic/libraries/architecture/viewmodel/viewmodel-factories
131+
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
132+
@Suppress("UNCHECKED_CAST")
133+
override fun <T : ViewModel> create(
134+
modelClass: Class<T>,
135+
extras: CreationExtras
136+
): T {
137+
// Get Remote Config instance.
138+
val dynamicLinks = Firebase.dynamicLinks
139+
return DynamicLinksViewModel(dynamicLinks) as T
140+
}
141+
}
142+
}
143+
}

0 commit comments

Comments
 (0)