Our android app is a chat app. Users can paste a branch link in a chat message. When another user taps on it, we want to retrieve the link parameters to take the user to another screen.
Unfortunately, we are unable to retrieve the link parameters when we tap on such link inside the app (note that we are not using a webview), we are getting the error "Warning. Session initialisation already happened.
To force a new session, set intent extra, branch_force_new_session, to true in the onInitFinished(#Nullable JSONObject referringParams, #Nullable BranchError error) method.
How can we solve this? It's not obvious to me how I could pass a new intent param in that use case.
Notes:
Our launcher activity is singleTask
We are on branch.io sdk 4.3.2
onNewIntent() does not seem to be called (in the code below), maybe that is the root cause for our issue.
sample code:
private Branch.BranchReferralInitListener branchReferralInitListener =
new Branch.BranchReferralInitListener() {
#Override
public void onInitFinished(#Nullable JSONObject referringParams, #Nullable BranchError error) {
...
}
#Override
protected void onStart() {
super.onStart();
Branch.getInstance().initSession(branchReferralInitListener, getIntent() != null ?
getIntent().getData() : null, this);
}
#Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
this.setIntent(intent);
// activity will skip onStart, handle this case with reInitSession
Branch.getInstance().reInitSession(this, branchReferralInitListener);
}
This is a known issue with the Android SDK v4.3.2 and we are working on a resolution.
Would suggest you to try the following in the meanwhile:
When the user tries to open an app that is running in the background, we get an error for set branch_force_new_session to true.
Branch SDK gets initialised on onStart for the Launcher Activity and when the app comes foreground from the background, its on onResume.
In this scenario, we could need to re-initialise the SDK here.
Would request you to implement the below snippet as per Branch docs (https://docs.branch.io/apps/android/#initialize-branch)
// activity will skip onStart, handle this case with reInitSession
Branch.getInstance().reInitSession(this, branchReferralInitListener);
Alternatively, would suggest you to install Branch SDK v4.3.1.
Initialized, you branch IO in application class so that it will initialize once and will not be require again
// Branch logging for debugging
Branch.enableLogging();
//Disable Device ID #2966
Branch.disableDeviceIDFetch(true);
// Initialize the Branch object
BranchIOManager.setupBranchInstance(this);
// It tells the Branch initialization to wait for the Google Play Referrer before proceeding.
Branch.enablePlayStoreReferrer(1000L);
Then inside initSession() branch method use. Pass them as JSON Object to method where you can retrieve the value based on key names.
if (branch != null && uri != null) {
branch.initSession(new Branch.BranchUniversalReferralInitListener() {
#Override
public void onInitFinished(BranchUniversalObject branchUniversalObject, LinkProperties linkProperties, BranchError error) {
// Log.d("onInitFinished", error + "");
if (error == null && branchUniversalObject != null) {
JSONObject jBranch = branchUniversalObject.getContentMetadata().convertToJson();
if (!branchJSONString.equals(jBranch.toString())) {
//This check is applied as if we launch another mLandingScreenPhoneActivity from Branch link then app will become in loop
branchJSONString = jBranch.toString();
loadScreenFromBranchIODynamicLink(jBranch, 0);
}
}
if (error != null) {
// //Toast.makeText(LandingScreenPhoneActivity.this, error + "", Toast.LENGTH_SHORT).show();
}
}
}, uri, this);
}
Here you can get screen values
String screenName = referringParams.optString("screen_key");
//Screen in app where needs to navigate
int screenIndex = referringParams.optInt("screen_index");
Related
I decided to experiment with MAUI. I am approaching first an Android App, and using Shell for navigation.
My App has 2 ways of opening:
When it's opened by the user tapping on the icon
Through a deep link, triggered by another app.
The issue I'm having is that when the app is triggered through the Deep link, I need to navigate to a specific page. I am trying to do it on the OnNewIntent accessing the Current instance of Shell, but when doing GoToAsync("my_route") it gives an error when trying to navigate to the new page.
This is what I have on my MainActivity:
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
var action = intent.Action;
var data = intent.DataString;
if (!string.IsNullOrWhiteSpace(data) && data.Contains("/data/")) {
if(Shell.Current != null)
{
Shell.Current.GoToAsync("myroute)";
// Also tried:
// - Shell.Current.GoToAsync("myroute").Wait();
// - App.Current.Dispatcher.Dispatch(async () => await Shell.Current.GoToAsync("//myroute")); (suggested by #toolmakersteve )
}
}
}
And this is the error:
Java.Lang.IllegalArgumentException: 'No view found for id 0x1
(unknown) for fragment ShellItemRenderer{19d353d}
(6c8560ab-dd58-4cbf-9e8b-2b9e12315f45 id=0x1)'
I'm assuming this has something to do with the fact that what I'm doing is not possible, so I need to find the RIGHT way to navigate to a specific page from OnNewIntent on MAUI, using Shell navigation.
UPDATE: It's also important to note that when the Deep Link triggers the app to open, there are two different behaviours:
If the app was already running, it throws the above mentioned exception
If the app was not already running, it opens regularly on the main screen, with no errors, but I would expect it to navigate to the desired Page.
Thanks!
First, make sure that GoToAsync("myroute") works if you use it somewhere more typical, such as a button press.
Assuming that works, then perhaps the intent code isn't running in the Dispatcher context (previously known as MainThread). Try:
Dispatcher.Dispatch(() => {
Shell.Current.GoToAsync("myroute");
});
VERSION 2
Perhaps deep link logic runs BEFORE App's OnResume.
If so, this might work:
In App.xaml.cs:
public partial class App : Application
{
...
public static bool FromDeepLink;
protected override void OnResume()
{
base.OnResume();
if (FromDeepLink)
{
FromDeepLink = false;
MainPage = new MainPage();
Dispatcher.Dispatch(() =>
{
Shell.Current.GoToAsync("myroute");
});
}
}
}
Then in OnNewIntent:
if (!string.IsNullOrWhiteSpace(data) && data.Contains("/data/")) {
App.FromDeepLink = true;
}
Conceptually #ToolmakerSteve answer is correct, but the OnResume event of the Application class seems not to fire when the app is resumed by intent (seems to be a Maui bug), however Android's native OnResume works and fires correctly even when the app is resumed via intent, all you have to do is in the MainActivity class to override Android's native OnResume method:
protected override void OnResume()
{
base.OnResume();
var fromDeepLink = Preferences.Get("FromDeepLink", false);
if (fromDeepLink)
{
Preferences.Set("FromDeepLink", false);
Shell.Current.GoToAsync("myroute");
}
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
var action = intent.Action;
var data = intent.DataString;
if (!string.IsNullOrWhiteSpace(data) && data.Contains("/data/"))
{
Preferences.Set("FromDeepLink", true);
}
}
I'm handling branch io on Android now.
It's weird. If I close my app first and click the link, the link leads me to my app and opens the page which is supposed to be shown. But If I open my app and click the home button, and click the link, the link leads me to ap.. but the page is not shown. I just could see the main page without routing by branch io.
here this is my code.
#Override
protected void onStart() {
super.onStart();
branchIO();
}
private void branchIO() {
Branch branch= Branch.getInstance();
branch.initSession(new Branch.BranchReferralInitListener(){
#Override
public void onInitFinished(JSONObject referringParams, BranchError error) {
if (error == null) {
try{
Log.d("log", referringParams.toString());
//...my routing logic...
}catch(Exception e){
Log.e("log", "branch io error",e);
}
} else {
Log.i("log", error.getMessage());
}
}
}, this.getIntent().getData(), this);
BranchIO.branchUniversalObject.generateShortUrl(this, BranchIO.linkProperties, new Branch.BranchLinkCreateListener() {
#Override
public void onLinkCreate(String url, BranchError error) {
if (error == null) {
}
}
});
}
If I close my app first, the log is this
D/log: {"$og_title":"₩230000", "~creation_source":5, "$og_description":"blah blah", "+click_timestamp":1512100123,........"}
but If I open my app first and put it in the background, the log is this
D/log: {"+clicked_branch_link":false,"+is_first_session":false}
I read many StackOverflow and GitHub pages but couldn't find the solution.
Thanks for reading!
EDIT
I forgot to use this.setIntent(intent) in onNewIntent.
so after adding this, It worked well.
#Override
public void onNewIntent(Intent intent) {
this.setIntent(intent);
}
Thanks!
Aaron from Branch.io here.
There might be a few reasons you are seeing this error. Here are a few:
You aren't initializing Branch and handling deep linking in your
Main/Splash activity.
Your Main/Splash activity does not have the
launchMode set to singleTask
You aren't overriding onNewIntent()
in your Main/Splash activity
If you are using a
CustomApplicationClass, make sure you are initializing Branch with
Branch.getAutoInstance(this);
You can find an example Main/Splash activity here.
You can also check out our testbed application which is a complete working example of the Branch Android SDK here
I have a pretty straight forward setting, setup
my intent filters for my main activity on the manifest
singleTask mode for all my activities (it just have two)
My app have two entry points: one the intent filter will call my MainActivity which start the branch session return the values on the referringParams and I go to the SecondActivity, everyone is happy
the another entry point is the launcher, when I click open the MainActivity do somethhing different because intent.data is empty and Go to SecondActivity, the problem is as follows, after the app is in the SecondActivity and the app goes background (e.g. touch home button) and then tap on some link the MainActivity is launched intent.data is not empty there's a valid url but when my callback is called I got referringParams empty {}
I dont know what is wrong with this. i have spend some hours without success
#Override
public void onStart() {
super.onStart();
Branch branch = Branch.getInstance();
branch.initSession(new Branch.BranchReferralInitListener(){
#Override
public void onInitFinished(JSONObject referringParams, BranchError error) {
if (error == null) {
// here referringParams is a empty {} object
} else {
Log.i("MyApp", error.getMessage());
}
}
}, this.getIntent().getData(), this);
}
#Override
public void onNewIntent(Intent intent) {
this.setIntent(intent);
}
I am not sure what processing you are doing in your Main Activity. The use case you mentioned, should return the Branch link parameters correctly,
If you have the intent filters and the launchMode of the MainActivity, correctly defined
You are overriding the onNewIntent() method in your MainActivity (which I can see from the code snippet you shared)
I created a sample app, which is uploaded here. If you follow the test case you mentioned with this app(i.e. App is backgrounded with the SecondActivity), clicking on a Branch link returns the link parameters correctly.
I am trying to display the Drop-in UI in my app upon clicking a specific button. I have used the guide from Braintree site but for some reason nothing is happening.
Code below:
OnClick function:
public void onClick(View v){
switch (v.getId()){
case R.id.showUI_button:
onBraintreeSubmit(v);
break;
}
}
Drop-in functions:
public void onBraintreeSubmit(View v) {
PaymentRequest paymentRequest = new PaymentRequest()
.clientToken(token)
.amount("$10.00")
.primaryDescription("Awesome payment")
.secondaryDescription("Using the Client SDK")
.submitButtonText("Pay");
startActivityForResult(paymentRequest.getIntent(this), REQUEST_CODE);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE) {
if (resultCode == BraintreePaymentActivity.RESULT_OK) {
PaymentMethodNonce paymentMethodNonce = data.getParcelableExtra(
BraintreePaymentActivity.EXTRA_PAYMENT_METHOD_NONCE
);
String nonce = paymentMethodNonce.getNonce();
// Send the nonce to your server.
}
}
}
I have checked that the token is returned from the server.
I have also tried by setting the onClick via the xml code of the button and removing the onClick from the java file but the result is the same, no UI shown.
The log has only two lines
performCreate Call Injection Manager
Timeline: Activity_idle id:android.os.BinderProxy#etc
Any ideas? If more info is needed to understand better let me know
Actually I found this there is a "BraintreeFragment" set up part. Braintree documentation needs to be more clear on this I think.
https://developers.braintreepayments.com/guides/client-sdk/setup/android/v2
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
mBraintreeFragment = BraintreeFragment.newInstance(this, mAuthorization);
// mBraintreeFragment is ready to use!
} catch (InvalidArgumentException e) {
// There was an issue with your authorization string.
}
}
The above code should work along with the previous code posted. mAuthorization is the token and needs to be valid to show the payment screen (so the variable "token" in the previous code posted which in my code I just have as private but visible from the whole activity).
Try with the test token that they have on their page and if this works then the main setup is ok.
https://developers.braintreepayments.com/start/hello-client/android/v2
For setting up tokens on your server, they have further documentation so that those test tokens work on the sandbox.
Please assist in this. I can't seem to create a suitable test for this method:
protected void startInterfacing() {
mLiveAuthClient.login(mView.context(), Arrays.asList(SCOPES), new LiveAuthListener() {
#Override
public void onAuthComplete(final LiveStatus liveStatus, final LiveConnectSession liveConnectSession,
Object o) {
// Login successful and user consented, now retrieve user ID and connect with backend server
getUserIdAndConnectWithBackendServer(liveConnectSession, mLiveAuthClient);
}
#Override
public void onAuthError(LiveAuthException e, Object o) {
// We failed to authenticate with auth service... show error
if (e.getError().equals("access_denied") ||
e.getMessage().equals("The user cancelled the login operation.")) {
// When user cancels in either the login or consent page, we need to log the user out to enable
// the login screen again when trying to connect later on
logUserOut(mLiveAuthClient, false);
} else {
onErrorOccured();
}
}
});
}
I'll explain abit what goes on here:
I'm trying to authenticate my client and log into OneDrive.
The method starts with a call to the Live SDK's login method. That SDK object is given to me from outside this class. So I can basically mock it.
Here's what I'm struggling with:
I do not need to test the call to the login method because it is not mine. I do need to test the call to getUserIdAndConnectWithBackendServer() inside onAuthComplete. But this method requires a liveConnectSession object. How do I provide that? It is given to me on the onAuthComplete method.
How do I mock the calls to onAuthComplete and onAuthError? I read about ArgumentCaptor but when I use that, I need to provide the arguments to those methods when I call the actual method.
For instance, argument.getValue().onAuthComplete() requires me to add arguments to this call. What do I actually provide here?
Here is the next method which is roughly the same but has its own issues:
protected void getUserIdAndConnectWithBackendServer(final LiveConnectSession liveConnectSession, final LiveAuthClient
authClient) {
final LiveConnectClient connectClient = new LiveConnectClient(liveConnectSession);
connectClient.getAsync("me", new LiveOperationListener() {
#Override
public void onComplete(LiveOperation liveOperation) {
// We got a result. Check for errors...
JSONObject result = liveOperation.getResult();
if (result.has(ERROR)) {
JSONObject error = result.optJSONObject(ERROR);
String code = error.optString(CODE);
String message = error.optString(MESSAGE);
onErrorOccured();
} else {
connectWithBackend(result, liveConnectSession, authClient);
}
}
#Override
public void onError(LiveOperationException e, LiveOperation liveOperation) {
// We failed to retrieve user information.... show error
onErrorOccured();
logUserOut(authClient, false);
}
});
}
In here I would like to mock the JSONObject for instance. But how do I call the onComplete method, or the onError method. And what would I provide as the arguments the methods provide me with. LiveOperation for instance?
Thank you!!
The solution I eventually used was to use mockito's doAnswer() structure.
This enabled me to get the callback argument and call one of its methods.
Another solution was to use an ArgumentCator.