My app launches a SplashActivity followed by a MainActivity. I run branch.initSession in the SplashActivity but it's taking about 1.5 seconds to return to the listener which delays the launch of the MainActivity. I would like to reduce this time.
My ideas are:
run branch.initSession in the MainActivity instead.
run branch.initSession in SplashActivity, launch MainActivity, then pass the branch to the MainActivity using an eventbus for processing.
Does anyone have any recommendations on how to solve this issue?
Cheers, Duane.
Amruta from Branch here.
By default, Branch will delay the install call only for up to 1.5 seconds. We delay the install call in order to capture the install referrer string passed through Google Play, which increases attribution and deferred deep linking accuracy. We do not delay any other call, and the install call only occurs the first time a user opens your app.
If we receive the referrer string before 1.5 seconds, we will immediately fire the call, meaning this delay is up to 1.5 seconds, but not guaranteed to take that long.
If you’d like to optimize the first install call, simply paste the following code in your Application class, and we will not delay the first install call.
public final class CustomApplicationClass {
#Override
public void onCreate() {
super.onCreate();
// initialize the Branch object
Branch.setPlayStoreReferrerCheckTimeout(0);
Branch.getAutoInstance(this);
}
}
My solution was:
If first launch, wait for the parameters in the Splash Activity
If not first launch, pass the Branch parameters on to the Application which would pass the parameters onto any listener to process. If there is no listener, then it can try to process it in the Splash Activity or save it for later.
I'm not sure if this is good practice, but I thought it'll be simpler than adding a Event Bus library. Would be happy to hear some feedback.
The Application class:
public class MyApp extends Application {
private Listener branchListener = null;
public interface Listener {
void onBranchLinkReceived(JSONObject params, BranchError error);
}
public void registerBranchListener(Listener listener) {
branchListener = listener;
}
public void unregisterBranchListener() {
branchListener = null;
}
public boolean branchLinkReceived(JSONObject params, BranchError error) {
if (branchListener != null) {
branchListener.onBranchLinkReceived(params, error);
return true;
}
return false;
}
#Override
public void onCreate() {
Branch.getAutoInstance(this);
...
}
}
In the Splash Activity:
public class SplashActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
Branch branch = Branch.getInstance();
branch.initSession(new Branch.BranchReferralInitListener(){
#Override
public void onInitFinished(JSONObject params, BranchError error) {
if (error == null) {
if (firstLaunch) {
processParamsInSplashActivity(params);
} else {
boolean isProcessed = ((MyApp) getApplication()).branchLinkReceived(params, error);
}
}
}, this.getIntent().getData(), this);
}
}
}
And in the MainActivity:
public class MainActivity extends AppCompatActivity implements MyApp.Listener {
#Override
protected void onCreate(Bundle savedInstanceState) {
((MyApp) getApplication()).registerBranchListener(this);
...
}
#Override
protected void onDestroy() {
((MyApp) getApplication()).unregisterBranchListener();
super.onDestroy();
}
#Override
public void onBranchLinkReceived(JSONObject params, BranchError error) {
Log.d("MainActivity", "Branch link received: " + params);
}
}
Related
There is a String message as a parameter in an interface method:
public class Home extends AppCompatActivity {
private String globalStringResult = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.home_activity);
getMediaInfo();
Log.d("Result: ", globalStringResult); // Here result is Null
}//TODO OnCreate End
private void getMediaInfo() {
FFmpeg.getInstance(this).execute(new String[]{"-version"},
new ExecuteBinaryResponseHandler() {
#Override
public void onSuccess(String message) {
globalStringResult = message;
}
});
}
}
Here is this problem that I've faced many times but always ran from it. Now I want to deal with it if you help me.
I am executing getMediaInfo() method inside onCreate. When I log the result inside onCreate after getMediaInfo() execution , the result would be null. But if I run it inside an onClick button or something I get my desired result.
Is there any way that I could return callback message anywhere that I want?
Sounds like your function getMediaInfo is asynchronous which means it could take some time before the onSuccess block is called and your value is set. Instead of relying on a variable I would suggest to use a callback function. That way your message will be passed to the callback function and you could use it anywhere.
public interface MyCallback{
void success(String message);
}
Then you would need to modify your function as follows. Then where ever the callback is implemented you will receive the message and you can act on the value.
public void getMediaInfo(MyCallback callback){
FFmpeg.getInstance(this).execute(cmdArray, new ExecuteBinaryResponseHandler() {
#Override
public void onSuccess(String message) {
callback.success(message);
}
});
If your further actions depend on the value set in onSuccess callback then simply call a function from this callback method. You need to provide more info on what exactly you are trying to do with this variable.
Asynchronous calls can be tricky but you have to wait until it is finished before the variable is available. This means calling any methods that rely on the variable in the callback of the async call. There's really no way around it. You may want to make two version of the call; one for onCreate and one when you need to call it from other places.
public class Home extends AppCompatActivity {
private String globalStringResult = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.home_activity);
getMediaInfoOnCreate();
// move any code below this line into the new method
}//TODO OnCreate End
private void getMediaInfo() {
FFmpeg.getInstance(this).execute(new String[]{"-version"},
new ExecuteBinaryResponseHandler() {
#Override
public void onSuccess(String message) {
globalStringResult = message;
codeThatNeedsToBeRunDuringOnCreateButAfterSettingGlobalStringResult();
}
});
}
private void getMediaInfoOnCreate() {
FFmpeg.getInstance(this).execute(new String[]{"-version"},
new ExecuteBinaryResponseHandler() {
#Override
public void onSuccess(String message) {
globalStringResult = message;
}
});
}
private void codeThatNeedsToBeRunDuringOnCreateButAfterSettingGlobalStringResult() {
// put your moved code from onCreate here
Log.d("Result: ", globalStringResult); // Here correct result will be logged
}
}
I have a method goToNextScreen() which does a check for 3 different asynchronous process, so when all process are done the validation will changes activity (Is kind of a Splash activity)
My example code access from 3 different result callbacks to the activity's method goToNextScreen() updating the flag value for each process and to validate other flags inside.
So far this approach works but i have the next questions:
Is this approach valid? Does it have a risk for some kind of deadlock? all threads/callbacks won't collide accessing the method at the same time causing wrong validations?
class LoadingActivity extends Activity{
public boolean isFetchDone, isAnimationDone, isServiceDone, watchDog;
Presenter presenter;
protected void onCreate(#Nullable Bundle savedInstanceState) {
presenter(this);
runAnimation();
presenter.runGetXService();
runFetchFromDB();
}
//do some generic isAnimationDone
private void runAnimation(){
//animator set and animation assumed to be correct...
//...
animatorSet.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
// do anything before animation start
}
#Override
public void onAnimationEnd(Animator animation) {
isAnimationDone = true;
goToNextScreen();
}
#Override
public void onAnimationCancel(Animator animation) {
// do something when animation is cancelled (by user/ developer)
}
#Override
public void onAnimationRepeat(Animator animation) {
// do something when animation is repeating
}
});
}
//Some example function to fetch data from x DB
private void runFetchFromDB() {
final Realm realm = RealmProvider.getInstance();
final ThingDB db = new ThingDBImpl(realm);
realm.beginTransaction();
db.getData(10L)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<XData>() {
#Override
public void onCompleted() {
isFetchDone = true;
goToNextScreen();
}
#Override
public void onError(Throwable e) {
//we dont care about the result
isFetchDone = true;
goToNextScreen();
}
#Override
public void onNext(XData dataSaved) {
//Should i update isFetchDone here?
});
realm.cancelTransaction();
}
private synchronized void goToNextScreen(){
if(!watchDog) return;
if(isFetchDone && isAnimationDone && isServiceDone){
changeActivityFaded(this, SomeOtherActivity);
finish();
watchDog = true;
}
}
}
class Presenter {
Activity activity;
Presenter(Activity activity){
this.activity = activity;
}
public void runGetXService(){
new Presenter.GetServiceAsyncTask().execute();
}
private class GetServiceAsyncTask extends AsyncTask<Void, Void, Response> {
#Override
protected void onPreExecute() {
super.onPreExecute();
//do preparation for service response, etc, asumme all correct
}
#Override
protected XResponse doInBackground(Void... voids) {
try {
return //Assume correct behaviour...
} catch (NetworkConnectionException e) {
return null;
}
}
#Override
protected void onPostExecute(XResponse xResponse) {
super.onPostExecute(xResponse);
((LoadingActivity)activity).isServiceDone = true;
((LoadingActivity)activity).goToNextScreen();
}
}
}
EDIT:
I have changed the method goToNextScreen to synchronized so it supposed to not allow access from others threads at the same time. Still have doubts if withs the execution will be right.
Yes, making the method synchronized means it cannot be executed multiple times simultaneously. If a second thread calls it while it is still executing, the second thread will block until the synchronization lock is released.
https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html
So far this approach by adding the synchronized to the method goToNextScreen member of the activity instance shared on the threads accessing it has worked so far. (View the question code for solution). Although i need to add a watch dog just in case some thread pass to execute code that its supposed to execute just once.
I'd like to make an initialization request when my app starts up. I then want to use the response in my MainActivity. I don't want to make that request in the Activity and then deal with the Activity lifecycle when the phone gets rotated.
So I was thinking of deriving from Application and making the request there. But what's the best way to send the response data to the my launcher Activity?
Is there a "best practice" solution here?
You could try using a library like Event Bus in order to receive the data inside your activity once your request task is complete. By doing this you wouldn't have to worry about where the call is made from or if your activity is rotated or recreated.
If the data is specificly for your MainActivity I would recommend having the request be triggered from there for the sake of keeping things coupled.
If you're looking for best practices, you shouldn't extend an Application class for this.
There is many ways to persist your request state on screen rotation.
Consider to use a retained Fragment. This approach is deeply discussed:
Understanding Fragment's setRetainInstance(boolean)
Further understanding setRetainInstance(true)
All you need to do is this:
1. Fragment class
public class RequestFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// This will be a guarantee that request is sent only once,
// because fragment won't be recreated on screen rotation:
setRetainInstance(true);
// Pereform sending request here.
}
}
2. Activity class
public class MainActivity extends AppCompatActivity {
private final static TAG_FRAGMENT = "persistent_fragment";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
// Create fragment if it doesn't exist yet.
if (fm.findFragmentByTag(TAG_FRAGMENT) == null) {
fm.beginTransaction()
.add(new RequestFragment(), TAG_FRAGMENT)
.commit();
}
}
}
But if you strongly decided to perform the request in Application onCreate() method, you have to implement an observable object that responds to activity which is subscribed to it, because you can't access an Activity from the Application class.
You can try this:
1. ResponseObservable class
public class ResponseObservale {
private MainActivity activity;
private Response response;
public void sendRequest() {
// perform your async request here.
}
/*
* Consider this method as a point where the response is delivered.
* It can be done in onPostExecute of AsyncTask or somewhere else,
* depending on your implementation.
*/
public void onResponse(Response response) {
this.response = response;
publishResponse();
}
public void onActivityCreated(MainActivity activity) {
this.activity = activity;
if (response != null) {
publishResponse();
}
}
private void publishResponse() {
if (activity != null) {
activity.obtainResponse(response);
}
}
public void onActivityDestroy() {
activity = null;
}
}
2. Application class
public class MyApplication extends Application {
private ResponseObservable observable;
#Override
public void onCreate() {
super.onCreate();
observable = new ResponseObservable();
observable.sendRequest();
}
public ResponseObservable getObservable() {
return observable;
}
}
3. Activity class
public class MainActivity extends AppCompatActivity {
private ResponseObserbale observable;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyApplication app = (MyApplication) getApplicationContext();
observable = app.getObservable();
observable.onActivityCreated(this);
}
public void obtainResponse(Response response) {
// Manage your response here.
}
#Override
protected void onDestroy() {
observable.onActivityDestroy();
}
}
Don't forget to declare your Application class in AndroidManifest.xml:
<application
android:name="com.yournamespace.appname.MyApplication"
android:icon="#drawable/icon"
android:label="#string/app_name">
Problem
The idea is very simple. Whenever an user comes back to my app from the Recents I want to show a simple dialog prompting with the password.
I know how to prompt the dialog with password, but my problem is how do I understand that the user has entered my app from the recents. If I put the prompt in the onResume in every activity, then it will get triggered everytime even if the user doesn't enter from the Recents menu.
There are lots of activities and fragments in my app. So, I would love to have a more generic or application level solution.
Implement Application.ActivityLifecycleCallbacks, that will provide all activity callback in your application class.
public class AppController extends Application implements
Application.ActivityLifecycleCallbacks
{
#Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(this);
}
#Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
#Override
public void onActivityStarted(Activity activity) {
}
#Override
public void onActivityResumed(Activity activity) {
}
#Override
public void onActivityPaused(Activity activity) {
}
#Override
public void onActivityStopped(Activity activity) {
}
#Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
#Override
public void onActivityDestroyed(Activity activity) {
}
}
You could try with this flag FLAG_ACTIVITY_LAUNCHER_FROM _HISTORY:
if((getIntent().getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY )!=0){
Log.d(TAG, "Called from history");
//clear flag from history
Intent intent = getIntent().setFlags( getIntent().getFlags() & (~ Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY));
setIntent(intent);
}
Source : Android - detecting application launch from home or history
When "A" Activity is start from recent, this flag is present.
Now this flag will be also present if "A" activity call "B" activity and on "B" user press back.
So you have to check flag and when you detect it you have clear intent by removing this flag, source: Remove a Paint Flag in Android
Try below sample
/**
* TODO : After update to API level 14 (Android 4.0),
* We should implement Application.ActivityLifecycleCallbacks
*/
public class GlobalApplication extends android.app.Application
{
private boolean inForeground = true;
private int resumed = 0;
private int paused = 0;
public void onActivityResumed( Activity activity )
{
++resumed;
if( !inForeground )
{
// Don't check for foreground or background right away
// finishing an activity and starting a new one will trigger to many
// foreground <---> background switches
//
// In half a second call foregroundOrBackground
}
}
public void onActivityPaused( Activity activity )
{
++paused;
if( inForeground )
{
// Don't check for foreground or background right away
// finishing an activity and starting a new one will trigger to many
// foreground <---> background switches
//
// In half a second call foregroundOrBackground
}
}
public void foregroundOrBackground()
{
if( paused >= resumed && inForeground )
{
inForeground = false;
}
else if( resumed > paused && !inForeground )
{
inForeground = true;
}
}
}
Put below code in your all activities.
public class BaseActivity extends android.app.Activity
{
private GlobalApplication globalApplication;
#Override
protected void onCreate()
{
globalApplication = (GlobalApplication) getApplication();
}
#Override
protected void onResume()
{
super.onResume();
globalApplication.onActivityResumed(this);
}
#Override
protected void onPause()
{
super.onPause();
globalApplication.onActivityPaused(this);
}
#Override
public void onDestroy()
{
super.onDestroy();
}
}
I would suggest using LifecycleObserver. If your Application class implements this interface it marks a class as a LifecycleObserver, it does not have any methods, instead, it relies on OnLifecycleEvent annotated methods. The usage is simple:
public class AndroidApplication extends Application implements LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onAppStart() {
//enter code here
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onAppStop() {
//enter code here
}
...etc
}
With Lifecycle.Event you can access all lifecycle states through Enum. It is part of androidx.
I've implemented something similar for iOS using a quick swizzle of viewDidAppear to allow my company to track flow around the app in our own internal systems.
Now, ideally I'd like to avoid having to implement appear & disappear tracking in every activity for our internal use, so was hoping someone could shed some light into how the GA library achieves this.
I had a good google around and couldn't find any kind of internal event that's posted when an activity comes into the foreground so am at a bit of a loss at the moment.
Cheers!
I know the following is not a direct answer to your question but why not applying a basic OOP principle? Inheritance.
import android.app.Activity;
public class BaseActivity extends Activity {
#Override
protected void onResume() {
super.onResume();
// hit when activity appears. Tell Appserver!
}
#Override
protected void onPause() {
super.onPause();
// hit when activity hides. Tell Appserver!
}
}
and then have your other Acivities extend this instead of android.app.Activity?
You can use ActivityLifecycleCallback. Example below:
public class MordorApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleLogger());
}
}
public class ActivityLifecycleLogger implements Application.ActivityLifecycleCallbacks {
#Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
createActivityStateLog(activity, "created");
}
#Override
public void onActivityStarted(Activity activity) {
createActivityStateLog(activity, "started");
}
#Override
public void onActivityResumed(Activity activity) {
createActivityStateLog(activity, "resumed");
}
#Override
public void onActivityPaused(Activity activity) {
createActivityStateLog(activity, "paused");
}
#Override
public void onActivityStopped(Activity activity) {
createActivityStateLog(activity, "stopped");
}
#Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
createActivityStateLog(activity, "savingStateInstance");
}
#Override
public void onActivityDestroyed(Activity activity) {
createActivityStateLog(activity, "destroyed");
}
private void createActivityStateLog(Activity activity, String state) {
String log = String.format("Activity %s - %s", activity.getClass().getName(), state);
LOG.debug(log);
}
private static final Logger LOG = LoggerFactory.getLogger(ActivityLifecycleLogger.class);
}
Google Analytics has automatic Activity tracking feature.
See
https://developers.google.com/analytics/devguides/collection/android/v4/?hl=en#analytics-xml
or do John's answer :)
You can create delegate class (suppose GaUtils) then call that on resume/pause. Just one line, don't you?
#Override
protected void onPause() {
super.onPause();
GaUtils.onPause(screenName); // track pause
}