diff --git a/CHANGELOG.md b/CHANGELOG.md index ec17e353..d25ba3fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +## 0.2.0 +* Add Referral System Rewarding Functionality ## 0.1.5 * Bugfix Branch SDK initialization ## 0.1.4 diff --git a/README.md b/README.md index 7eab7809..0beb8239 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Implemented functions in plugin: * Register view * Track User Actions and Events * Init Branch Session and Deep Link +* Referral rewards ## Getting Started ### Configure Branch Dashboard @@ -120,7 +121,7 @@ Generates a deep link within your app if (response.success) { print('Link generated: ${response.result}'); } else { - print('Error : ${response.errorCode} - ${response.errorDescription}'); + print('Error : ${response.errorCode} - ${response.errorMessage}'); } ``` ### Show Share Sheet deep link @@ -137,7 +138,7 @@ Note: _For Android additional customization is possible_ if (response.success) { print('showShareSheet Sucess'); } else { - print('Error : ${response.errorCode} - ${response.errorDescription}'); + print('Error : ${response.errorCode} - ${response.errorMessage}'); } ``` ### List content on Search @@ -205,6 +206,12 @@ FlutterBranchSdk.setIdentity('user1234567890'); //logout FlutterBranchSdk.logout(); ``` +```dart +//check if user is identify + bool isUserIdentified = await FlutterBranchSdk.isUserIdentified(); +``` + + ### Enable or Disable User Tracking If you need to comply with a user's request to not be tracked for GDPR purposes, or otherwise determine that a user should not be tracked, utilize this field to prevent Branch from sending network requests. This setting can also be enabled across all users for a particular link, or across your Branch links. ```dart @@ -215,10 +222,107 @@ FlutterBranchSdk.disableTracking(true); ``` You can choose to call this throughout the lifecycle of the app. Once called, network requests will not be sent from the SDKs. Link generation will continue to work, but will not contain identifying information about the user. In addition, deep linking will continue to work, but will not track analytics for the user. +### Referral System Rewarding Functionality +Reward balances change randomly on the backend when certain actions are taken (defined by your rules), so you'll need to make an asynchronous call to retrieve the balance. +Read more here: https://docs.branch.io/viral/referrals/#search + + +#### Get Reward Balance +Reward balances change randomly on the backend when certain actions are taken (defined by your rules), so you'll need to make call to retrieve the balance. Here is the syntax: + +***optional parameter***: bucket - value containing the name of the referral bucket to attempt to redeem credits from + +```dart +BranchResponse response = + await FlutterBranchSdk.loadRewards(); +if (response.success) { + credits = response.result; + print('Crédits'); +} else { + print('Credits error: ${response.errorMessage}'); +} +``` + + +#### Redeem All or Some of the Reward Balance (Store State) +Redeeming credits allows users to cash in the credits they've earned. Upon successful redemption, the user's balance will be updated reflecting the deduction. + +***optional parameter***: bucket - value containing the name of the referral bucket to attempt to redeem credits from + +```dart +BranchResponse response = + await FlutterBranchSdk.redeemRewards(count: 5); +if (response.success) { + print('Redeeming Credits with success'); +} else { + print('Redeeming Credits with error: ${response.errorMessage}'); +} +``` +#### Get Credit History +This call will retrieve the entire history of credits and redemptions from the individual user. To use this call, implement like so: + +***optional parameter***: bucket - value containing the name of the referral bucket to attempt to redeem credits from + +```dart +BranchResponse response = + await FlutterBranchSdk.getCreditHistory(); +if (response.success) { + print('getCreditHistory with success. Records: ${(response.result as List).length}'); +} else { + print('getCreditHistory with error: ${response.errorMessage}'); +} +``` +The response will return an list of map: +```json +[ + { + "transaction": { + "date": "2014-10-14T01:54:40.425Z", + "id": "50388077461373184", + "bucket": "default", + "type": 0, + "amount": 5 + }, + "event" : { + "name": "event name", + "metadata": { your event metadata if present } + }, + "referrer": "12345678", + "referree": null + }, + { + "transaction": { + "date": "2014-10-14T01:55:09.474Z", + "id": "50388199301710081", + "bucket": "default", + "type": 2, + "amount": -3 + }, + "event" : { + "name": "event name", + "metadata": { your event metadata if present } + }, + "referrer": null, + "referree": "12345678" + } +] +``` +**referrer** : The id of the referring user for this credit transaction. Returns null if no referrer is involved. Note this id is the user id in a developer's own system that's previously passed to Branch's identify user API call. + +**referree** : The id of the user who was referred for this credit transaction. Returns null if no referree is involved. Note this id is the user id in a developer's own system that's previously passed to Branch's identify user API call. + +**type** : This is the type of credit transaction. + +* 0 - A reward that was added automatically by the user completing an action or promo. +* 1 - A reward that was added manually. +* 2 - A redemption of credits that occurred through our API or SDKs. +* 3 - This is a very unique case where we will subtract credits automatically when we detect fraud. + + # Getting Started See the `example` directory for a complete sample app using Branch SDK. -![Example app](https://user-images.githubusercontent.com/17687286/70445281-0b87c180-1a7a-11ea-8611-7217d46c75a7.png) +![Example app](https://user-images.githubusercontent.com/17687286/74096674-725c4200-4ae0-11ea-8ef6-94bc02e1913b.png) # Branch Universal Object best practices diff --git a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java index 0953d5d9..fc618fe2 100644 --- a/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java +++ b/android/src/main/java/br/com/rsmarques/flutter_branch_sdk/FlutterBranchSdkPlugin.java @@ -8,6 +8,7 @@ import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.json.JSONArray; import org.json.JSONException; @@ -249,6 +250,18 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { case "validateSDKIntegration": validateSDKIntegration(); break; + case "loadRewards": + loadRewards(call, result); + break; + case "redeemRewards": + redeemRewards(call, result); + break; + case "getCreditHistory": + getCreditHistory(call, result); + break; + case "isUserIdentified": + isUserIdentified(result); + break; default: result.notImplemented(); } @@ -312,7 +325,7 @@ public void onLinkCreate(String url, BranchError error) { } else { response.put("success", false); response.put("errorCode", String.valueOf(error.getErrorCode())); - response.put("errorDescription", error.getMessage()); + response.put("errorMessage", error.getMessage()); } result.success(response); } @@ -358,7 +371,7 @@ public void onLinkShareResponse(String sharedLink, String sharedChannel, BranchE } else { response.put("success", Boolean.valueOf(false)); response.put("errorCode", String.valueOf(error.getErrorCode())); - response.put("errorDescription", error.getMessage()); + response.put("errorMessage", error.getMessage()); } result.success(response); } @@ -464,6 +477,127 @@ private void setTrackingDisabled(MethodCall call) { Branch.getInstance().disableTracking(value); } + private void loadRewards(final MethodCall call, final Result result) { + + final Map response = new HashMap<>(); + Branch.getInstance(context).loadRewards(new Branch.BranchReferralStateChangedListener() { + @Override + public void onStateChanged(boolean changed, @Nullable BranchError error) { + int credits; + if (error == null) { + if (!call.hasArgument("bucket")) { + credits = Branch.getInstance(context).getCredits(); + } else { + credits = Branch.getInstance(context).getCreditsForBucket(call.argument("bucket").toString()); + } + response.put("success", Boolean.valueOf(true)); + response.put("credits", credits); + } else { + response.put("success", Boolean.valueOf(false)); + response.put("errorCode", String.valueOf(error.getErrorCode())); + response.put("errorMessage", error.getMessage()); + } + result.success(response); + } + }); + } + + private void redeemRewards(final MethodCall call, final Result result) { + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + + final int count = call.argument("count"); + final Map response = new HashMap<>(); + + if (!call.hasArgument("bucket")) { + Branch.getInstance(context).redeemRewards(count, new Branch.BranchReferralStateChangedListener() { + @Override + public void onStateChanged(boolean changed, @Nullable BranchError error) { + if (error == null) { + response.put("success", Boolean.valueOf(true)); + } else { + response.put("success", Boolean.valueOf(false)); + response.put("errorCode", String.valueOf(error.getErrorCode())); + response.put("errorMessage", error.getMessage()); + } + result.success(response); + } + }); + } else { + Branch.getInstance(context).redeemRewards(call.argument("bucket").toString(), count, new Branch.BranchReferralStateChangedListener() { + @Override + public void onStateChanged(boolean changed, @Nullable BranchError error) { + if (error == null) { + response.put("success", Boolean.valueOf(true)); + } else { + response.put("success", Boolean.valueOf(false)); + response.put("errorCode", String.valueOf(error.getErrorCode())); + response.put("errorMessage", error.getMessage()); + } + result.success(response); + } + }); + } + } + + private void getCreditHistory(final MethodCall call, final Result result) { + if (!(call.arguments instanceof Map)) { + throw new IllegalArgumentException("Map argument expected"); + } + final Map response = new HashMap<>(); + + if (!call.hasArgument("bucket")) { + Branch.getInstance(context).getCreditHistory(new Branch.BranchListResponseListener() { + @Override + public void onReceivingResponse(JSONArray list, BranchError error) { + if (error == null) { + response.put("success", Boolean.valueOf(true)); + JSONObject jo = new JSONObject(); + try { + jo.put("history", list); + response.put("data", paramsToMap(jo)); + } catch (JSONException e) { + e.printStackTrace(); + } + } else { + response.put("success", Boolean.valueOf(false)); + response.put("errorCode", String.valueOf(error.getErrorCode())); + response.put("errorMessage", error.getMessage()); + } + result.success(response); + } + }); + } else { + Branch.getInstance(context).getCreditHistory(call.argument("bucket").toString(), new Branch.BranchListResponseListener() { + @Override + public void onReceivingResponse(JSONArray list, BranchError error) { + if (error == null) { + response.put("success", Boolean.valueOf(true)); + JSONObject jo = new JSONObject(); + try { + jo.put("history", list); + response.put("data", paramsToMap(jo)); + } catch (JSONException e) { + e.printStackTrace(); + } + + } else { + response.put("success", Boolean.valueOf(false)); + response.put("errorCode", String.valueOf(error.getErrorCode())); + response.put("errorMessage", error.getMessage()); + } + result.success(response); + } + }); + } + } + + private void isUserIdentified(Result result) { + result.success(Branch.getInstance(context).isUserIdentified()); + } + + /**--------------------------------------------------------------------------------------------- Object Conversion Functions --------------------------------------------------------------------------------------------**/ diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index f8ee7522..42bdbf73 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -52,13 +52,5 @@ android:name="io.branch.sdk.BranchKey.test" android:value="key_test_ipQTteg11ENANDeCzSXgqdgfuycWoXYH" /> - - - - - - diff --git a/example/lib/main.dart b/example/lib/main.dart index 01aabe20..09d4709f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -29,7 +29,10 @@ class _MyAppState extends State { void initState() { super.initState(); + FlutterBranchSdk.setIdentity('branch_user_test'); + listenDynamicLinks(); + initDeepLinkData(); } @@ -147,6 +150,7 @@ class _MyAppState extends State { } void showSnackBar({@required String message, int duration = 3}) { + scaffoldKey.currentState.removeCurrentSnackBar(); scaffoldKey.currentState.showSnackBar(SnackBar( content: Text(message), duration: Duration(seconds: duration), @@ -162,7 +166,7 @@ class _MyAppState extends State { title: const Text('Branch.io Plugin Example App'), ), body: ListView( - physics: const NeverScrollableScrollPhysics(), + //physics: const NeverScrollableScrollPhysics(), padding: EdgeInsets.all(10), children: [ StreamBuilder( @@ -228,7 +232,7 @@ class _MyAppState extends State { child: RaisedButton( child: Text('Identify user'), onPressed: () { - FlutterBranchSdk.setIdentity('user1234567890'); + FlutterBranchSdk.setIdentity('branch_user_test'); }, ), ), @@ -300,9 +304,6 @@ class _MyAppState extends State { ), ], ), - SizedBox( - height: 5, - ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -337,6 +338,100 @@ class _MyAppState extends State { ), ], ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: RaisedButton( + child: Text('Viewing Credits'), + onPressed: () async { + bool isUserIdentified = + await FlutterBranchSdk.isUserIdentified(); + + if (!isUserIdentified) { + showSnackBar(message: 'User not identified'); + return; + } + + int credits = 0; + BranchResponse response = + await FlutterBranchSdk.loadRewards(); + if (response.success) { + credits = response.result; + print('Crédits'); + showSnackBar(message: 'Credits: $credits'); + } else { + showSnackBar( + message: 'Credits error: ${response.errorMessage}'); + } + }, + ), + ), + SizedBox( + width: 10, + ), + Expanded( + child: RaisedButton( + child: Text('Redeeming Credits'), + onPressed: () async { + bool isUserIdentified = + await FlutterBranchSdk.isUserIdentified(); + + print('isUserIdentified: $isUserIdentified'); + + if (!isUserIdentified) { + showSnackBar(message: 'User not identified'); + return; + } + bool success = false; + BranchResponse response = + await FlutterBranchSdk.redeemRewards( + count: 5); + if (response.success) { + success = response.result; + print('Redeeming Credits: $success'); + showSnackBar(message: 'Redeeming Credits: $success'); + } else { + print( + 'Redeeming Credits error: ${response.errorMessage}'); + showSnackBar( + message: + 'Redeeming Credits error: ${response.errorMessage}'); + } + //success = await + }, + ), + ), + ], + ), + RaisedButton( + child: Text('Get Credits Hystory'), + onPressed: () async { + bool isUserIdentified = + await FlutterBranchSdk.isUserIdentified(); + + print('isUserIdentified: $isUserIdentified'); + + if (!isUserIdentified) { + showSnackBar(message: 'User not identified'); + return; + } + + BranchResponse response = + await FlutterBranchSdk.getCreditHistory(bucket: "teste"); + if (response.success) { + print('Credits Hystory: ${response.result}'); + showSnackBar( + message: + 'Check log for view Credit History. Records: ${(response.result as List).length}'); + } else { + print( + 'Get Credits Hystory error: ${response.errorMessage}'); + showSnackBar( + message: + 'Get Credits Hystory error: ${response.errorMessage}'); + } + }), RaisedButton( child: Text('Generate Link'), onPressed: generateLink, @@ -404,7 +499,7 @@ class _MyAppState extends State { controllerUrl.sink.add('${response.result}'); } else { controllerUrl.sink - .add('Error : ${response.errorCode} - ${response.errorDescription}'); + .add('Error : ${response.errorCode} - ${response.errorMessage}'); } } @@ -421,7 +516,7 @@ class _MyAppState extends State { } else { showSnackBar( message: - 'showShareSheet Error: ${response.errorCode} - ${response.errorDescription}', + 'showShareSheet Error: ${response.errorCode} - ${response.errorMessage}', duration: 5); } } diff --git a/example/pubspec.lock b/example/pubspec.lock index 45f99ac7..fffbc48e 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -75,7 +75,7 @@ packages: path: ".." relative: true source: path - version: "0.1.5" + version: "0.2.0" flutter_test: dependency: "direct dev" description: flutter diff --git a/ios/Classes/SwiftFlutterBranchSdkPlugin.swift b/ios/Classes/SwiftFlutterBranchSdkPlugin.swift index b2a8f639..2eb2930b 100644 --- a/ios/Classes/SwiftFlutterBranchSdkPlugin.swift +++ b/ios/Classes/SwiftFlutterBranchSdkPlugin.swift @@ -137,6 +137,16 @@ public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStream case "validateSDKIntegration": validateSDKIntegration() break + case "loadRewards": + loadRewards(call: call, result: result) + break + case "redeemRewards": + redeemRewards(call: call, result: result) + break + case "getCreditHistory": + getCreditHistory(call: call, result: result) + case "isUserIdentified": + isUserIdentified(result: result) default: result(FlutterMethodNotImplemented) } @@ -162,7 +172,7 @@ public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStream response["success"] = NSNumber(value: false) if let err = (error as NSError?) { response["errorCode"] = String(err.code) - response["errorDescription"] = err.localizedDescription + response["errorMessage"] = err.localizedDescription } } result(response) @@ -187,7 +197,7 @@ public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStream response["success"] = NSNumber(value: false) if let err = (error as NSError?) { response["errorCode"] = String(err.code) - response["errorDescription"] = err.localizedDescription + response["errorMessage"] = err.localizedDescription } } result(response) @@ -283,4 +293,107 @@ public class SwiftFlutterBranchSdkPlugin: NSObject, FlutterPlugin, FlutterStream let value = args["disable"] as! Bool Branch.setTrackingDisabled(value) } + + private func loadRewards(call: FlutterMethodCall, result: @escaping FlutterResult) { + let args = call.arguments as! [String: Any?] + let response : NSMutableDictionary! = [:] + + Branch.getInstance().loadRewards { (changed, error) in + if (error == nil) { + var credits : Int = 0 + if let bucket = args["bucket"] as? String { + credits = Branch.getInstance().getCreditsForBucket(bucket) + } else { + credits = Branch.getInstance().getCredits() + } + response["success"] = NSNumber(value: true) + response["credits"] = credits + } else { + print(error) + let err = (error as! NSError) + response["success"] = NSNumber(value: false) + response["errorCode"] = String(err.code) + response["errorMessage"] = err.localizedDescription + } + result(response) + } + } + + private func redeemRewards(call: FlutterMethodCall, result: @escaping FlutterResult) { + let args = call.arguments as! [String: Any?] + let count = args["count"] as! Int + let response : NSMutableDictionary! = [:] + + if let bucket = args["bucket"] as? String { + Branch.getInstance().redeemRewards(count, forBucket: bucket, callback: {(success, error) in + if success { + response["success"] = NSNumber(value: true) + } + else { + print("Failed to redeem credits: \(error)") + let err = (error as! NSError) + response["success"] = NSNumber(value: false) + response["errorCode"] = String(err.code) + response["errorMessage"] = err.localizedDescription + } + result(response) + }) + } else { + Branch.getInstance().redeemRewards(count, callback: {(success, error) in + if success { + response["success"] = NSNumber(value: true) + } + else { + print("Failed to redeem credits: \(error)") + let err = (error as! NSError) + response["success"] = NSNumber(value: false) + response["errorCode"] = String(err.code) + response["errorMessage"] = err.localizedDescription + } + result(response) + }) + } + } + + private func getCreditHistory(call: FlutterMethodCall, result: @escaping FlutterResult) { + let args = call.arguments as! [String: Any?] + let response : NSMutableDictionary! = [:] + let data : NSMutableDictionary! = [:] + + if let bucket = args["bucket"] as? String { + Branch.getInstance().getCreditHistory(forBucket: bucket, andCallback: { (creditHistory, error) in + if error == nil { + data["history"] = creditHistory + response["success"] = NSNumber(value: true) + response["data"] = data + } else { + print("Failed to redeem credits: \(error)") + let err = (error as! NSError) + response["success"] = NSNumber(value: false) + response["errorCode"] = String(err.code) + response["errorMessage"] = err.localizedDescription + } + result(response) + }) + } else { + Branch.getInstance().getCreditHistory { (creditHistory, error) in + if error == nil { + data["history"] = creditHistory + response["success"] = NSNumber(value: true) + response["data"] = data + } else { + print("Failed to redeem credits: \(error)") + let err = (error as! NSError) + response["success"] = NSNumber(value: false) + response["errorCode"] = String(err.code) + response["errorMessage"] = err.localizedDescription + } + result(response) + } + } + } + + private func isUserIdentified(result: @escaping FlutterResult) { + result(Branch.getInstance().isUserIdentified()) + } } diff --git a/lib/src/branch_response.dart b/lib/src/branch_response.dart index de778223..7837b4d5 100644 --- a/lib/src/branch_response.dart +++ b/lib/src/branch_response.dart @@ -1,16 +1,21 @@ part of flutter_branch_sdk; -class BranchResponse { +class BranchResponse { bool success; - String result; + T result; String errorCode; - String errorDescription; + String errorMessage; BranchResponse.success({@required this.result}) { this.success = true; } BranchResponse.error( - {@required this.errorCode, @required this.errorDescription}) { + {@required this.errorCode, @required this.errorMessage}) { this.success = false; } + + @override + String toString() { + return ('sucess: $success, errorCode: $errorCode, errorMessage: $errorMessage}'); + } } diff --git a/lib/src/branch_sdk.dart b/lib/src/branch_sdk.dart index ee163372..e0eb0e3b 100644 --- a/lib/src/branch_sdk.dart +++ b/lib/src/branch_sdk.dart @@ -101,7 +101,7 @@ class FlutterBranchSdk { } else { return BranchResponse.error( errorCode: response['errorCode'], - errorDescription: response['errorDescription']); + errorMessage: response['errorMessage']); } } @@ -140,10 +140,11 @@ class FlutterBranchSdk { } else { return BranchResponse.error( errorCode: response['errorCode'], - errorDescription: response['errorDescription']); + errorMessage: response['errorMessage']); } } + ///Logs this BranchEvent to Branch for tracking and analytics static void trackContent( {@required BranchUniversalObject buo, BranchEvent branchEvent}) { if (buo == null) { @@ -211,4 +212,72 @@ class FlutterBranchSdk { } return await _messageChannel.invokeMethod('removeFromSearch', _params); } + + ///Retrieves rewards for the current user/session + static Future loadRewards({String bucket}) async { + Map _params = {}; + if (bucket != null) _params['bucket'] = bucket; + + Map response = + await _messageChannel.invokeMethod('loadRewards', _params); + + if (response['success']) { + return BranchResponse.success(result: response['credits']); + } else { + return BranchResponse.error( + errorCode: response['errorCode'], + errorMessage: response['errorMessage']); + } + } + + ///Redeems the specified number of credits. if there are sufficient credits within it. + ///If the number to redeem exceeds the number available in the bucket, all of the + ///available credits will be redeemed instead. + static Future redeemRewards( + {@required int count, String bucket}) async { + if (count == null) { + throw ArgumentError('Count credits is required'); + } + + Map _params = {}; + _params['count'] = count; + if (bucket != null) _params['bucket'] = bucket; + + Map response = + await _messageChannel.invokeMethod('redeemRewards', _params); + + if (response['success']) { + return BranchResponse.success(result: true); + } else { + return BranchResponse.error( + errorCode: response['errorCode'], + errorMessage: response['errorMessage']); + } + } + + ///Gets the credit history + static Future getCreditHistory({String bucket}) async { + Map _params = {}; + if (bucket != null) _params['bucket'] = bucket; + + Map response = + await _messageChannel.invokeMethod('getCreditHistory', _params); + + print('GetCreditHistory ${response.toString()}'); + + if (response['success']) { + return BranchResponse.success(result: response['data']['history']); + } else { + return BranchResponse.error( + errorCode: response['errorCode'], + errorMessage: response['errorMessage']); + } + } + + ///Indicates whether or not this user has a custom identity specified for them. Note that this is independent of installs. + ///If you call setIdentity, this device will have that identity associated with this user until logout is called. + ///This includes persisting through uninstalls, as we track device id. + static Future isUserIdentified() async { + return await _messageChannel.invokeMethod('isUserIdentified'); + } } diff --git a/pubspec.yaml b/pubspec.yaml index 8b0a8d2b..dcb6e535 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_branch_sdk description: Flutter Plugin for create deep link using Brach SDK (https://branch.io). This plugin provides a cross-platform (iOS, Android). -version: 0.1.5 +version: 0.2.0 homepage: https://github.com/RodrigoSMarques/flutter_branch_sdk dependencies: