Skip to content

Commit

Permalink
feat/Implement SDK universal links and app links
Browse files Browse the repository at this point in the history
  • Loading branch information
František Gažo committed Jul 28, 2021
1 parent aa02ec4 commit 0f1e766
Show file tree
Hide file tree
Showing 19 changed files with 229 additions and 29 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ android {
* [Basics concepts](./documentation/BASIC_CONCEPTS.md)
* [Configuration](./documentation/CONFIGURATION.md)
* [Tracking](./documentation/TRACKING.md)
* [Tracking Campaigns(Android App Links/Universal links)](./documentation/LINKING.md) - TODO: Not supported yet!
* [Tracking Campaigns(Android App Links/Universal links)](./documentation/LINKING.md)
* [Fetching](./documentation/FETCHING.md)
* [Push notifications](./documentation/PUSH.md)
* [Anonymize customer](./documentation/ANONYMIZE.md)
Expand Down
11 changes: 8 additions & 3 deletions android/src/main/kotlin/com/exponea/ExponeaPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class ExponeaPlugin : FlutterPlugin, ActivityAware {
private const val STREAM_NAME_RECEIVED_PUSH = "$CHANNEL_NAME/received_push"

fun handleCampaignIntent(intent: Intent?, context: Context) {
// TODO-EXF-8 : Exponea.handleCampaignIntent(intent, context)
Exponea.handleCampaignIntent(intent, context)
}
}

Expand Down Expand Up @@ -240,13 +240,18 @@ private class ExponeaMethodHandler(private val context: Context) : MethodCallHan
}
}

private fun configure(args: Any?, result: Result) = runWithNoResult(result) {
requireNotConfigured()
private fun configure(args: Any?, result: Result) = runWithResult<Boolean>(result) {
try {
requireNotConfigured()
} catch (e: Exception) {
return@runWithResult false
}
val data = args as Map<String, Any?>
val configuration = ExponeaConfigurationParser().parseConfig(data)
Exponea.init(activity ?: context, configuration)
this.configuration = configuration
Exponea.notificationDataCallback = { ReceivedPushStreamHandler.handle(ReceivedPush(it)) }
return@runWithResult true
}

private fun isConfigured(result: Result) = runWithResult<Boolean>(result) {
Expand Down
53 changes: 53 additions & 0 deletions documentation/LINKING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Tracking Campaigns (Android App Links/Universal links)
The official [Flutter documentation](https://flutter.dev/docs/development/ui/navigation/deep-linking) describes how to set up your application and how to process incoming link, we just need to add tracking to Exponea.
> When the application is opened by App Link/Universal link and there is no session active, started session will contain tracking parameters from the link.
WARNING: The official deep-linking support in flutter sdk does not currently work properly for iOS.
(Github issue: https://github.com/flutter/flutter/issues/82550)
If the issue is still not fixed in your flutter version, then usage of flutter plugin [uni_links](https://pub.dev/packages/uni_links) is recommended.
See exponea example app code and [installation steps](https://pub.dev/packages/uni_links) for more info.

## Android
Android linking works automagically without any changes required. To enable Exponea tracking you need to add 2 methods to the `MainActivity` that will respond to incoming intents.
```kotlin
package com.exponea.example

import android.content.Intent
import android.os.Bundle
import com.exponea.ExponeaPlugin
import io.flutter.embedding.android.FlutterActivity

class MainActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Add this call:
ExponeaPlugin.Companion.handleCampaignIntent(intent, applicationContext)
super.onCreate(savedInstanceState)
}

override fun onNewIntent(intent: Intent) {
// Add this call:
ExponeaPlugin.Companion.handleCampaignIntent(intent, applicationContext)
super.onNewIntent(intent)
}
}
```

## iOS
Linking requires you to call `SwiftExponeaPlugin.continueUserActivity(userActivity)` function in your `AppDelegate`.

### With ExponeaFlutterAppDelegate
If your `AppDelegate` already extends `ExponeaFlutterAppDelegate` no change is required.


### Without ExponeaFlutterAppDelegate
If you don't use the `ExponeaFlutterAppDelegate`, you need to implement the method and call `SwiftExponeaPlugin`.
```swift
open override func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
SwiftExponeaPlugin.continueUserActivity(userActivity)
return super.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
```
21 changes: 21 additions & 0 deletions example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,27 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Deep linking -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="old.panaxeo.com"
android:pathPattern="/exponea/.*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with "exponea://”-->
<data android:scheme="exponea" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import io.flutter.embedding.android.FlutterActivity

class MainActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// TODO-EXF-8 ExponeaPlugin.Companion.handleCampaignIntent(intent, applicationContext)
ExponeaPlugin.Companion.handleCampaignIntent(intent, applicationContext)
super.onCreate(savedInstanceState)
}

override fun onNewIntent(intent: Intent) {
// TODO-EXF-8 ExponeaPlugin.Companion.handleCampaignIntent(intent, applicationContext)
ExponeaPlugin.Companion.handleCampaignIntent(intent, applicationContext)
super.onNewIntent(intent)
}
}
6 changes: 6 additions & 0 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ PODS:
- Quick (4.0.0)
- shared_preferences (0.0.1):
- Flutter
- uni_links (0.0.1):
- Flutter

DEPENDENCIES:
- exponea (from `.symlinks/plugins/exponea/ios`)
Expand All @@ -19,6 +21,7 @@ DEPENDENCIES:
- Nimble (= 8.0.7)
- Quick (= 4.0.0)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
- uni_links (from `.symlinks/plugins/uni_links/ios`)

SPEC REPOS:
trunk:
Expand All @@ -35,6 +38,8 @@ EXTERNAL SOURCES:
:path: Flutter
shared_preferences:
:path: ".symlinks/plugins/shared_preferences/ios"
uni_links:
:path: ".symlinks/plugins/uni_links/ios"

SPEC CHECKSUMS:
AnyCodable-FlightSchool: d27283db6e9feaddb0fa73a25835e5cdf41b3213
Expand All @@ -45,6 +50,7 @@ SPEC CHECKSUMS:
Nimble: a73af6ecd4c9106f434f3d55fc54570be3739e0b
Quick: 6473349e43b9271a8d43839d9ba1c442ed1b7ac4
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a

PODFILE CHECKSUM: 7d1d5194d74e70f57aba3a9a46925dbdf19e6d3e

Expand Down
11 changes: 0 additions & 11 deletions example/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,6 @@
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>exponea</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
Expand Down
1 change: 1 addition & 0 deletions example/ios/Runner/Runner.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<array>
<string>webcredentials:panaxeo.com</string>
<string>applinks:panaxeo.com</string>
<string>applinks:old.panaxeo.com</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
Expand Down
58 changes: 54 additions & 4 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,65 @@
import 'package:flutter/material.dart';
import 'dart:async';

import 'page/config.dart';
import 'page/home.dart';
import 'package:exponea_example/page/config.dart';
import 'package:exponea_example/page/home.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:uni_links/uni_links.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
StreamSubscription? _linkSub;

@override
void initState() {
_handleInitialLink();
_handleIncomingLinks();
super.initState();
}

Future<void> _handleInitialLink() async {
try {
final initialLink = await getInitialLink();
if (initialLink != null) {
_showSnackBarMessage('App opened with link: $initialLink');
}
} on PlatformException catch (err) {
print('initialLink: $err');
}
}

void _handleIncomingLinks() {
_linkSub = linkStream.listen((String? link) {
_showSnackBarMessage('App resumed with link: $link');
}, onError: (err) {
_showSnackBarMessage('App resume with link failed: $err');
});
}

void _showSnackBarMessage(String text) {
final snackBar = SnackBar(content: Text(text));
_scaffoldMessengerKey.currentState!.showSnackBar(snackBar);
}

@override
void dispose() {
_linkSub?.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
return MaterialApp(
scaffoldMessengerKey: _scaffoldMessengerKey,
theme: ThemeData.from(
colorScheme: ColorScheme.light(
primary: Colors.amber,
Expand All @@ -29,6 +78,7 @@ class MyApp extends StatelessWidget {
},
),
),
builder: (context, child) => child!,
);
}
}
8 changes: 7 additions & 1 deletion example/lib/page/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,13 @@ class _ConfigPageState extends State<ConfigPage> {
),
);
try {
await _plugin.configure(config);
final configured = await _plugin.configure(config);
if (!configured) {
final snackBar = SnackBar(
content: Text('SDK was already configured'),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
widget.doneCallback.call(config);
} on PlatformException catch (err) {
final snackBar = SnackBar(
Expand Down
26 changes: 26 additions & 0 deletions example/lib/page/link.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';

class LinkPage extends StatelessWidget {
final String link;

const LinkPage({
Key? key,
required this.link,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Link'),
),
body: Center(
child: Text(link),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.done),
onPressed: () => Navigator.of(context).pop(),
),
);
}
}
21 changes: 21 additions & 0 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,27 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
uni_links:
dependency: "direct main"
description:
name: uni_links
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.1"
uni_links_platform_interface:
dependency: transitive
description:
name: uni_links_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
uni_links_web:
dependency: transitive
description:
name: uni_links_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.0"
vector_math:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies:
path: ../

shared_preferences: 2.0.6
uni_links: 0.5.1
cupertino_icons: ^1.0.3

dev_dependencies:
Expand Down
9 changes: 9 additions & 0 deletions ios/Classes/ExponeaFlutterAppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,13 @@ open class ExponeaFlutterAppDelegate: FlutterAppDelegate {
completionHandler([.alert])
}
}

open override func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
SwiftExponeaPlugin.continueUserActivity(userActivity)
return super.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
}
Loading

0 comments on commit 0f1e766

Please sign in to comment.