Branch onInitFinished callback called twice if network is not accessible - android

I have empty Splash activity serving as entry point to the Android application and invoking appropriate activities if Branch data is received.
In case of Branch callback error or missing or unrecognized data it invokes default Main activity.
It all works well if device has Internet connectivity, but in case of failure onInitFinished callback is called twice in a row (once with empty data set and once triggering error), invoking Main activity twice.
public class SplashActivity extends AppCompatActivity
{
Branch.BranchReferralInitListener branchCallback;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.splash_activity);
Log.d("XAPP", "Splash");
branchCallback = new Branch.BranchReferralInitListener()
{
#Override
public void onInitFinished(JSONObject referringParams, BranchError error)
{
Log.d("XAPP", "Branch init session");
if (error == null)
{
Log.d("XAPP", referringParams.toString());
// run different activities depending on the parameters
....
else
{
// fallback to Main activity
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
startActivity(intent);
}
}
else
{
Log.i("XAPP", error.getMessage());
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
startActivity(intent);
}
finish();
}
};
}
#Override
protected void onStart()
{
super.onStart();
Log.d("XAPP", "onStart");
Branch branch = Branch.getInstance();
branch.initSession(branchCallback);
}
}
Resulting logcat when application runs without being connected to the Internet (after it has been previously - at some point - opened through Branch deep link and Branch data has been initialized):
D/XAPP: Splash
D/XAPP: onStart
D/XAPP: Branch init session
D/XAPP: {"+is_first_session":false,"+clicked_branch_link":false}
D/XAPP: Branch init session
I/XAPP: Trouble initializing Branch. Branch API Error: poor network connectivity. Please try again later.
Splash activity is declared as singleTask activity and is started only once.
Relevant parts of AndroidManifest:
<uses-permission android:name="android.permission.INTERNET"/>
...
<activity
android:name=".SplashActivity"
android:launchMode="singleTask"
android:label="#string/app_name"
android:theme="#style/AppTheme.FullScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- Branch URI Scheme -->
<intent-filter>
<data android:host="open" android:scheme="xxxx"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:theme="#style/AppTheme.FullScreen">
</activity>
Relevant parts of the Gradle:
android {
compileSdkVersion 26
buildToolsVersion '27.0.3'
defaultConfig {
minSdkVersion 15
targetSdkVersion 26
...
dependencies {
compile('io.branch.sdk.android:library:2.14.4') {
exclude module: 'answers-shim'
}
I could solve the issue by making Main activity singleTask or removing the Splash activity altogether - by moving branching into Main activity, but those solutions are not viable options in this particular case.
One of the possible solutions would also be adding some boolean flag to recognize onInitFinished has already been called, but I would like to avoid that one if possible.
My main concern in this situation and actual question is not how to hack the thing to make it work, but why is onInitFinished called twice and is there a flaw in my Branch callback implementation?

I tested with your SplashActivity in a sample app and the callback was fired only once.
Here is my repo.
Please test with this app to check if you can replicate the behavior
In order to compile the app:
1. Add the URI scheme from your Branch dashboard to the Android Manifest
2. Add the Branch key for your app to the Manifest
3. Add your link domain to the app link filter in the Manifest.
Also, I would suggest upgrading the Branch SDK to the latest version i.e. 2.14.4.
If you have a slightly varied implementation, could you either share your Manifest file here. If not, you could also write into integrations#branch.io where the team could help you efficiently.

Single Task launch mode is required!
The reason for this is because if there is no singleTask Activity instance in the system yet, a new one would be created and simply placed on top of the stack in the same Task. If you are using the Single Task mode as is, it should not restart your entire app. The Single Task mode instantiates the Main/Splash Activity only if it does not exist in the Activity Stack. If the Activity exists in the background, every subsequent intent to the Activity just brings it to the foreground.

Related

Activity Lifecycle changed with API 25 (7.1.1)

In my MainActivity, I have a dialog which is opened if a flag in the intent is set. If the dialog was created, it is dismissed in onPause()
#Override
public void onPause() {
super.onPause();
if (_dialog!= null) {
_dialog.dismiss();
_dialog= null;
}
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intentContainsFlag) {
_dialog = ....;
_dialog.show();
}
}
The dialog is to be opened if a ListView holder's button is pressed and builds an intent URI:
bttn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// The URL scheme is registered in the intent filter
String intentString = "http://open.example.com/myParameters";
v.getContext().startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse(intentString)));
}
});
The AndroidManigfest contains:
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:launchMode="singleTask"
android:screenOrientation="landscape" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" android:host="open.example.com" android:pathPattern=".*"/>
<data android:scheme="https" android:host="open.example.com" android:pathPattern=".*"/>
</intent-filter>
....
The sdk versions are set to
minSdkVersion = 19
targetSdkVersion= 22
compileSdkVersion = 23
buildToolsVersion = 23
On Android < 7.1.1, everything works as expected: onNewIntent() is called and the dialog is visible.
But on 7.1.1. devices
the MainActivity's onNewIntent is called, then directly afterwards onPause and onResume. This means that the activity opens itself / comes to the foreground but the dialog was immediately closed.
A possible workaround is to close the dialog in onStop() but I don't get why this happens on Android 7.1.1 - was something changed in the life cycle ?
But on 7.1.1. devices the MainActivity's onNewIntent is called, then directly afterwards onPause and onResume. This means that the activity opens itself / comes to the foreground but the dialog was immediately closed.
The Android framework may destroy your activity any time it's in the background or backstack, and you should write your activities so they behave correctly when this happens. look at this :
Don't keep activities under the Developer Options menu. When this
option is enabled, the Android OS will destroy an activity as soon as
it is stopped. It is intended to help developers debug their apps. For
example, it can simulate the case that Android will kill an activity
in the background due to memory pressure. In normal use, it is not
recommended to turn this option on because this may lead to unexpected
issues on the apps, such as freezes, force closes and reboots.
Your dialog itself causes your activity to be paused and than closed.
It seems that differents are not in the Android version.
If you enable "Don't keep activities" flag in developers settings, then lifecycle will be next:
onCreate
onResume
* perform startActivityForResult
onPause
onDestroy
* returning result
onCreate
onResume
onPause
onNewIntent
onResume
Because onNewIntent always comes in a paused state.

Beginning an application while no starting activity set

I would like to launch my application and check the connectivity state in application's onCreate method then decide which activity to start! I know that I could finish() a default MAIN/LAUNCHER activity before to setLayout while starting another if it's relevant but that seems messy to me!
So, I would like to know if it is possible to start an application whose doesn't manifest an activity with action.MAIN / category.LAUNCHER? I tried this way but it doesn't work! I mean the application seems to start but no activity is shown!
(This is not a sample from my real code, I'm not at home right now! Some arguments and stuff may be missing but I think you get the point!)
public class MyApp extends Application {
onCreate() {
Intent intent = new Intent(this, MyActivity.class);
intent.setFlags(Intent.NEW_TASK);
this.startActivity(intent);
}
}
Also, the first activity of my application may be an AlertDialog and I'm wondering if I can start one while no activity is started or if I'm forced to set an activity's theme with #android:style/Theme.Dialog?
I tried the same as for the above example but same result : logcat saying application alive while no printing at all...
Tell me if I'm not clear enough and in which way! I'm not an english speaker and I'm not used to ask in forums!
You will have to go this way:
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.some_empty_or_loading_view); //optional probably, not sure
//TODO: check whatever you want
if(condition) {
startActivity(this, SomeActivity.class);
} else {
startActivity(this, AnotherActivity.class);
}
finish();
}
}
Specify Your App's Launcher Activity
When the user selects your app icon from the Home screen, the system calls the onCreate() method for the Activity in your app that you've declared to be the "launcher" (or "main") activity. This is the activity that serves as the main entry point to your app's user interface.
You can define which activity to use as the main activity in the Android manifest file, AndroidManifest.xml, which is at the root of your project directory.
The main activity for your app must be declared in the manifest with an that includes the MAIN action and LAUNCHER category. For example:
<activity android:name=".MainActivity" android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Note: When you create a new Android project with the Android SDK tools, the default project files include an Activity class that's declared in the manifest with this filter.
If either the MAIN action or LAUNCHER category are not declared for one of your activities, then your app icon will not appear in the Home screen's list of app

Facebook deep linking on Android

I'm trying to implement Facebook's Deep Linking feature on my app and encountered the following scenario:
I have an activity called MainActivity which is declared like so:
<activity
android:name="com.mypackage.android.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
This activity + my package name are also declared in my app's settings on facebook developer website.
Once a link gets clicked on Facebook's app, I'm supposed to handle this event via the onCreate method of my activity.
The following code handle the event:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri target = getIntent().getData();
if (target != null){
// got here via Facebook deep link
// once I'm done parsing the URI and deciding
// which part of my app I should point the client to
// I fire an intent for a new activity and
// call finish() the current activity (MainActivity)
}else{
// activity was created in a normal fashion
}
}
All goes according to plan except for the following scenario:
User launched my app
MainActivity created
SecondaryActivity created
MainActivity finished
App goes to background via the device home button
Deep link gets clicked on Facebook's app
In this case my app goes to foreground again, but MainActivity's onCreate / onNewIntent
don't get called, instead SecondaryActivity's onResume() gets called and restored to it's
last state.
Note: I've tested this issue on a Samsung Nexus with Android 4.2.1 and got to this result, though when tested on Galaxy S1 with Android 2.3.5 it worked as I initially expected.
Any help would be greatly appreciated,
Thank you.
Facebook is starting your app from their own app by explicitly start your "MainActivity" (the one your provided them in the developer page).
by that - Android's default behavior is: if the application already runs, then calling again to startActivity() won't start new task from scratch, but only restore to foreground the already running task.
but the good news are that you can change this default behavior by adding to your MainActivity the android:launchMode="singleTask". it definition is:
the system creates a new task and instantiates the activity at the root of the new task. However, if an instance of the activity already exists in a separate task, the system routes the intent to the existing instance through a call to its onNewIntent() method, rather than creating a new instance. Only one instance of the activity can exist at a time.
from this point you could always respond to the starting intent, and from that point you can always navigate back to the task that already was in background(if exists) by restarting activity with both flags Intent.FLAG_ACTIVITY_SINGLE_TOP && Intent.FLAG_ACTIVITY_CLEAR_TOP combination
See http://developer.android.com/guide/topics/manifest/activity-element.html
You can play with:
android:clearTaskOnLaunch
android:noHistory
android:launchMode
You need to have more information in your intent filter:
<intent-filter>
<action android:name="android.intent.action.VIEW"></action>
<category android:name="android.intent.category.DEFAULT"></category>
<category android:name="android.intent.category.BROWSABLE"></category>
<data android:host="www.yoursite.com" android:scheme="http"></data>
</intent-filter>
This will capture links going to your site (make sure to change the URL), and direct them to whatever Activity you define this intent filter under.

Android Application vs Activity

I have written a few Android apps, and have always declared a starting Activity as the:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
It would be great for scoping some global methods, statics, shared prefs, etc if I could start my app using an Application that then calls the first Activity from it's onCreate() after setting up prefs, etc, but I haven't been able to find any examples of this design pattern... when I try this in code, I get a ClassCastException:
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
// do stuff (prefs, etc)
// start the initial Activity
Intent i = new Intent(this, InitialActivity.class);
startActivity(i);
}
}
InitialActivity.class is indeed an Activity that works fine if I set it to be MAIN, but trying to start it from MyApplication that is declared MAIN generates the error. Probably a very silly question, but am I tackling this all wrong?
Thanks,
Paul
You can fix this by using FLAG_ACTIVITY_NEW_TASK flag:
Intent intent = new Intent(this, ApplicationActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
That's because you need to start new task when Activity is started outside of Activity context. But I strongly recommend to not start Activity from your Application's onCreate().
Android has 4 components: Activity, Service, ContentProvider and Broadcast.
When Android needs to activate one of this components from your application, it looks if there is already existing running process with your application. If not, then Android starts new process, initializes it, then it initializes your custom Application instance. And then it activates one of needed components.
Now, let's consider next scenario: your application declared content provider in AndroidManifest.xml, and Android just about to start your application so you can provide some data to another foreground application.
Content Provider request is sent
Your application wasn't running, and Android starts new process for it.
Your custom Application instance is created
Application.onCreate() is called.
You start an activity
Your Content Provider receives request
Somebody just wanted to connect to your content provider, but your application started an Activity instead. Same true for starting background Service and sometimes broadcast receivers.
And also consider if some other application's activity A wanted to started activity X from your application. But in onCreate() you started activity Y, and then X is also started by Android. Then user presses back. What should happen? Its tricky...
Starting activities from Application's onCreate may result in quite weird user experience. So don't do it.
UPDATE:
Because Android guarantees that Application will be created only once and before any other component, you can use next code to access your Application's single instance:
public class MyApplication extends Application
{
private static MyApplication s_instance;
public MyApplication()
{
s_instance = this;
}
public static MyApplication getApplication()
{
return s_instance;
}
}
Did you set it in you manifest activity tag for this intent you are starting (another one besides your main) ?
</activity>
<activity android:name=".InitialActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="com.package.INITACT" /> <--- this is only name by which you activity can be called.
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

Android Home activity launching a sub-activity

I am relatively new to Android programming. I have been given a task at work where I need to create a Custom Home activity launcher. I did a bit of research and found the example on the Android developer website (home sample). This was the start for my prototype.
The custom Home activity will need to require the user to authenticate (enter some simple credentials). My thought was to launch a sub-activity from the Home activity and pass back the results in the intent to the Home activity. I need to be able to capture information about these credentials and that information was going to be passed back to the Home activity. However, I have problems when trying this. I get a log in the LogCat that says the following: "Activity is launching as a new task, so canceling activity result."
I am aware of the startActivityForResult method, but that does not seem to be working for me. Here is where I launch the activity from my Home activity:
#Override
protected void onResume() {
super.onResume();
bindRecents();
Intent iii = new Intent(this, Login.class);
startActivityForResult(iii, STATIC_LOGIN_INTEGER_VALUE);
}
When that code executes, I get the above mentioned log from the ActivityManager tag.
My Login activity has a button that the user will hit once they have entered their credentials. If the credentials are good, then I try to do the following (I put in several logs so that I could try to figure out what is going on):
public void onClick(View src) {
// check for authentic credentials
if(IsValid())
{
Intent loginAuth = new Intent("Login");
loginAuth.putExtra("userRole", userRole);
Log.d("LOGIN", "Setting result...");
if (getParent() == null) {
Log.d("LOGIN", "Parent was null");
setResult(Activity.RESULT_OK, loginAuth);
}
else {
Log.d("LOGIN", "setting result on parent...");
getParent().setResult(Activity.RESULT_OK, loginAuth);
}
Log.d("LOGIN", "Finishing the activity");
finish();
}
}
I defined these activities in my manifest file as the following:
<activity android:name="Home"
android:theme="#style/Theme"
android:launchMode="singleInstance"
android:stateNotNeeded="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name="Login"
android:label="Login"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
</activity>
I was playing around with the intent filter on the Login activity. I had originally had it set to nothing. I also had the launchMode blank (which defaults to standard I believe). I have played around with most of these options and nothing seems to change the fact that the ActivityManager seems to want to launch the activity as a new task and wants to ignore the returned intent (which I need).
The problem is that you declared your activities with launchMode="singleInstance", so Android creates a new task (i.e. a new process) when it launches the Login activity. Activities in different tasks cannot return results to each other. From the Activity.startActivityForResult() docs:
For example, if the activity you are
launching uses the singleTask launch
mode, it will not run in your task and
thus you will immediately receive a
cancel result.
singleInstance is like singleTask but even more restrictive. Try removing the launchMode attribute from your manifest so that the Login activity will launch in the same task, then using FLAG_ACTIVITY_NEW_TASK when you need to launch a different activity in a separate task.
Barry
The documentation says
For example, if the activity you are launching uses the singleTask
launch mode, it will not run in your task and thus you will
immediately receive a cancel result.
THIS IS WRONG.
You can use the singleTask just fine, what you can't use is singleInstance.
Let's return to the problem. What is the problem?
The problem is that when you call startActivityForResult() on an activity, the caller activity and the activity you are launching must be on the same task for the startActivityForResult() to work properly that means that you can't call startActivityForResult() to an activity using the Intent.FLAG_ACTIVITY_NEW_TASK.
why singleInstance isn't working?
Because a "singleInstance" activity stands alone as the only activity in its task. If it starts another activity, that activity will be launched into a different task regardless of its launch mode — as if FLAG_ACTIVITY_NEW_TASK was in the intent. In all other respects, the "singleInstance" mode is identical to "singleTask".
Why singleTask is working?
Because singleTask doesn't require the creation of a new task for the new activities being launched.
I quoted from this answer to explain the difference between the two launch modes.
Have you declared the setResult(int resultCode, Intent data) in your Login activity.
Android Documentation says
"Note that this method should only be used with Intent protocols that are defined to return a result." If it is not or some other conditions matches as in the documentation of Activity.startActivityForResult(), it will not run in your task and thus you will immediately receive a cancel result."

Categories

Resources