The application(target API level must be 7th) has FragmentActivity which analyzes at onCreate the fragment key passed as an extra.
Now what is needed is to reorder to front the activity that is already created with the given fragment key.
Let's say the FragmentActivity with different fragment keys are FA1, FA2 and FA3 - each is the same activity Class instance with different fragments.
Now in the stack FA1 > FA2 > FA3 i want to use the intent rather than the back button to get to FA2, by default that gives:
FA1 > FA2 > FA3 > new FA2.
I'd like to get either FA1 > FA3 > FA2 as the FA3 might have some pending operations, FA1 > FA2 is not as good but definitely better than default.
If there were several activities I'd use the FLAG_ACTIVITY_REORDER_TO_FRONT flag for intents, but that does not work for this case.
FA1, FA2, FA3, etc. are all the instances of the same class MyFA, that's why I'm not able to use the intent flag and the FragmentManager seems to be out of help until there's a standard global fragments cache.
Milestone (currently working and to be improved) solution One thing I've learned today is activity-alias which allowed to make several aliases for the same activity with the different Intent extras used as id's. Now with the REORDER_TO_FRONT flag it works as I wanted.
Solution feedback The solution has no low-level operations, I like a lot more than digging at the tasks or back-stacks. Now the drawback is that each of such activities needs a separate alias with the hardcoded path, I don't really like it.
Requirements (bounty is here) Whoever comes with a decent optimization takes 150 300 cookies. Not bad ? Any other solid solution is also highly appreciated.
Currently I have like 10 aliases at application manifest, e.g.
<activity
android:name=".activity.FragmentActivity"
android:configChanges="orientation"
android:screenOrientation="portrait" >
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="com.company.name.intent.FragmentActivity" />
</intent-filter>
</activity>
<activity-alias
android:name="com.company.name.intent.FragmentActivity.FragmentedOne"
android:targetActivity=".activity.FragmentActivity" >
<intent-filter>
<action android:name="com.company.name.intent.FragmentActivity.FragmentedOne" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="fragment_key_extra"
android:value="FragmentOne" />
</activity-alias>
<activity-alias
android:name="com.company.name.intent.FragmentActivity.FragmentedTwo"
android:targetActivity=".activity.FragmentActivity" >
<intent-filter>
<action android:name="com.company.name.intent.FragmentActivity.FragmentedTwo" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="fragment_key_extra"
android:value="FragmentTwo" />
</activity-alias>
And then the activities are reordered with
Intent intent = new Intent(
"com.company.name.intent.FragmentActivity.FragmentedOne");
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
This answer is worth 300 cookies :-D Mmmmm
Extend Application so we can create our own back stack list with a global scope.
import android.app.Application;
import android.content.Intent;
class MyApp extends Application {
//This is our very own back stack.
private ArrayList<Intent> backStack = new ArrayList<Intent>();
public void reorderBackStack(){
//Do what you want to the order of your backstack
//You can read the value of your intent extras from here.
}
public void addIntent(Intent i){ // this gets called from our activities onCreate
backStack.add(i);
}
public void goBack(){
if(backStack.size() >= 2){ // can go back
backStack.remove(backStack.size() - 1); // drop the last item in stack if you want. This is how Android does it. (no forward capability)
this.startActivity(backStack.get(backStack.size() - 1));
}
}
}
Add a reference to our custom back stack in our activity.
public class MainActivity extends Activity {
MyApp myapp;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myapp = ((MyApp)getApplicationContext());
myapp.addIntent(this.getIntent()); //add the intent for this instance to backStack.
myapp.reorderBackStack(); // call this anytime we need to reorder the back stack.
}
#Override
public void onNewIntent(Intent newIntent){
this.setIntent(newIntent);
myapp.addIntent(this.getIntent());
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
#Override
// We won't be using the back stack we will be using our own list of intents as a back stack so we need to override the back button.
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
myapp.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
}
Override the back button so we can use our own back stack instead of Android's.
Edit
I put together a proof of concept.
Pros
No Aliases
Completely dynamic and reusable
Low level control
Easy to use (Extend CustomBackStackActivity instead of FragmentActivity or Activity)
Cons
Not an out-of-the-box solution. Will require tweaking and debugging.
The .apk is Here on my google drive.
The complete project folder is here.
Zipped downloadable project folder is here.
Please don't complain about my code. I know I used strings where I should have used constants and I duplicated code instead of seperating it and my spacing isn't perfect and I used excessive loops and objects. I haven't sorted out the savedInstanceState. Again, this is just a proof of concept. It works and I thought it might help someone.
You can look into using the functionality provided by the FragmentManager. It has functions such as popBackStack.
You can also use FragmentTransaction to add fragments to the backstack. You can use addToBackStack to accomplish this.
So with these two classes you should be able to reorder your fragments in the backstack as necessary.
Here is the solution:
1. In your FragmentActivity implement a Interface for the callback from Fragment to Activity.
2. When you start any Fragment put it in back stack with the argument or tag (addToBackStack(String arg0))with the help of which you can pop up with that tag.
3. When you need to reorder the fragments then call the method of the interface which you implemented with proper arguments as per requirement and then use popBackStack(String arg1, String arg2) to get the fragment with the tag you put it in the backstack.
Based on your idea of using activity-alias to solve this issue, I wrote a Historian class that will do the following:
Scan the Activity list in your package for aliases to your specific Activity.
Set up a lookup table that maps each alias to an Intent.
Provide a startActivity() and activityDestroyed() methods that will do some bookkeeping so the lookup table can be used to dynamically assign an alias to a running Activity based on the Intent.
Here's an example on how to use it:
in AndroidManifest.xml
<activity android:name=".MyFragmentActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity-alias android:name=".Alias0" android:targetActivity=".MyFragmentActivity" />
<activity-alias android:name=".Alias1" android:targetActivity=".MyFragmentActivity" />
<activity-alias android:name=".Alias2" android:targetActivity=".MyFragmentActivity" />
<activity-alias android:name=".Alias3" android:targetActivity=".MyFragmentActivity" />
<activity-alias android:name=".Alias4" android:targetActivity=".MyFragmentActivity" />
within your Activity class
public class MyFragmentActivity extends FragmentActivity
implements Historian.Host {
private Historian<MyFragmentActivity> mHistorian;
// ...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHistorian = new Historian<MyFragmentActivity>(this);
// ...
}
#Override
protected void onDestroy() {
super.onDestroy();
mHistorian.activityDestroyed(this);
}
#Override
public boolean matchIntent(Intent intent0, Intent intent1) {
if (intent0 == null || intent1 == null) return false;
final String title0 = intent0.getStringExtra("title");
final String title1 = intent1.getStringExtra("title");
return title0.equals(title1);
}
// ...
}
Instead of starting a new instance of your activity like this:
Intent intent = new Intent(this, MyFragmentActivity.class);
intent.putExtra("title", newActivityTitle);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
You do this:
Intent intent = new Intent(this, MyFragmentActivity.class);
intent.putExtra("title", newActivityTitle);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
mHistorian.startActivity(this, intent);
So you still need to add a few activity-alias into your manifest manually (to be used as a pool) and implement the matchIntent() method in your Activity (to help detect whether two Intents are equal to you) but the rest is handled dynamically by the Historian class.
I haven't tested the code exhaustively, but it seems to work fine on some simple tests that I did. The idea is actually very similar to my answer on the other question (just need to use FLAG_ACTIVITY_CLEAR_TOP instead of FLAG_ACTIVITY_REORDER_TO_FRONT there) but using the activity-alias instead of the inner child classes make it much cleaner :)
I have answered something similar, but the solution is not the best as FA1 > FA2 > FA3 will take you to FA1 > FA2 rather than FA1 > FA3 > FA2.
My answer was for question : How to tag Activity
Anyway, I edited the code a bit to use a "tag" which will be a string, but I think you can use the main solutions that uses integer indices. In summary, this should allow you to fallBackToActivity with a certain TAG. I am not sure how will each Activity decide what will its tag be, but again, in my previous answer it was a simple matter of an incremental integer.
package sherif.android.stack.overflow;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
public class SuperActivity extends FragmentActivity {
private static String EXTRA_INDEX = "SUPER_INDEX";
private static int RESULT_FALLBACK = 0x123456;
private String tag; //this is your tag
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(getIntent()!=null) {
tag = getIntent().getStringExtra(EXTRA_INDEX, "default_tag");
}
}
protected final String getIndex() {
return tag;
}
protected final void fallBackToActivity(String tag) {
Intent intent = new Intent();
intent.putExtra(EXTRA_INDEX, tag);
setResult(RESULT_FALLBACK, intent);
finish();
}
//#Override
//public void startActivityForResult(Intent intent, int requestCode) {
// intent.putExtra(EXTRA_INDEX, getIndex());
// super.startActivityForResult(intent, requestCode);
//}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(resultCode == RESULT_FALLBACK) {
if(!data.getStringExtra(EXTRA_INDEX, "default_tag").equals(getIndex())) {
setResult(RESULT_FALLBACK, data);
finish();
}
}
}
}
Related
On phones I need Activity C's parent to be Activity B and on tablets I need Activity C's parent to be Activity A. However, I can't set a string resource for the parentActivityName. Is there a way to do it via XML? Thanks!
<activity
android:name="com.example.app.ActivityC"
<!-- #string resource not accepted here -->
android:parentActivityName="com.example.app.ActivityB">
</activity>
As far as I know, there's no way. You might do it with your activity itself though. Create a new activity to handle starts on different devices
public class ActivityD {
protected void onCreate (Bundle savedInstanceState) {
boolean isTablet = false || true; // get configuration
Class<?> activity = isTablet ? ActivityB.class : ActivityA.class;
startActivity(new Intent(this, activity));
finish();
}
}
and define it as parent instead
<activity
android:name="com.example.app.ActivityC"
android:parentActivityName="com.example.app.ActivityD">
</activity>
It's not completely XML, but the best I could think about.
So My application Consists of first activity which shows some text, and on action bar there is a File menu, in which I put My location option.
I call another activity in mainActivity with onOptionItemSelected as follows:
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_photo:
openPhoto();
return true;
case R.id.action_video:
openVideo();
return true;
case R.id.action_map:
Intent intent = new Intent(this, GPSTracker.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
in manifest i declare the second activity as follows:
<activity
android:name="com.example.locateme.GPSTracker"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
and in GPSTracker.java i write this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gpstracker);
}
also there is my code for finding the location.
I am running the app, bu when i press My Location option the app crashes.
Here are the logcat errors after removing intent for GPS activity
The full code of the app is here, in case there may be something i missed.
Do i call the second activity in the wrong way?
java.lang.InstantiationException: can't instantiate class com.example.locateme.GPSTracker; no empty constructor
is quite obvious error.
Somewhere in your GPSTracker class you have a definition like
public GPSTracker(SomeClass referenceName) {
//...
}
This block of code should be removed or replaced with constructor without params. The first option if prefered: use onCreate as your constructor.
First, remove the intent-filter for Your GPS Activity in Your Manifest.xml, here You had set both activities (Main and GPS) as a launcher. Set only one Activity as LAUNCHER and MAIN. And then it will be good to see LogCat Output to know why it crashs
I'm using this code to jump back in activity stack (mainly to move to home Activity):
Intent goTo = new Intent(this, HomeActivity.class);
goTo.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(goTo);
So I create new Intent and set "target" to HomeActivity which is in Activity stack so whole stack will be cleared from top to this HomeActivity.
Now I need slightly different use case. I have for example five Activities A-B-C-D-E (A started B etc.) on the stack. Now I need to jump from E to C or B depending of what user choose. The problem is that Activities A, B, C, D, E have same class. So I can't use example above because I don't know how to target that Activity.
So the question is if there is any way how to "tag activity" or manipulate with stack.
Thanks!
I haven't tried it myself, but I think the best option would be to refactor your app to use a stack of Fragments within a single Activity (since you can then more easily manage the backstack using the provided addToBackStack() and popBackStack() methods). Basically this involves moving most of the code in your Activity into a Fragment and then adding the backstack manipulation code in the Activity). You can look at the code for FragmentBreadCrumbs (with API 11+) or the code for HanselAndGretel (for use with the compatibility library) to see how this can be implemented.
However, if you want to continue using your current multi-Activity approach, the following is some code I came up with to illustrate how you can do this.
First, add several internal classes to alias your current Activity and put these classes into a sequence list (note also the simplistic getSequencedActivityIntent() method that I wrote, you can add more advanced logic if you need - maybe use a HashMap to associate each class in the sequence with an arbitrary tag value?):
public class MyActivity extends Activity {
public static class A extends MyActivity {}
public static class B extends MyActivity {}
public static class C extends MyActivity {}
public static class D extends MyActivity {}
public static class E extends MyActivity {}
public static class F extends MyActivity {}
public static class G extends MyActivity {}
public static class H extends MyActivity {}
public static class I extends MyActivity {}
public static class J extends MyActivity {}
private final static List<Class<?>> SEQUENCE = Arrays.asList(new Class<?>[] {
A.class, B.class, C.class, D.class, E.class,
F.class, G.class, H.class, I.class, J.class,
});
private Intent getSequencedActivityIntent(int step) {
final int current = SEQUENCE.indexOf(this.getClass());
if (current == -1) new Intent(this, SEQUENCE.get(0));
final int target = current + step;
if (target < 0 || target > SEQUENCE.size() - 1) return null;
return new Intent(this, SEQUENCE.get(target));
}
// the rest of your activity code
}
Don't forget to add their entries to your AndroidManifest.xml file too (singleTop is optional - it will prevent the Activity instance in the stack to be created again when brought back to the front):
<activity android:name=".MyActivity$A" android:launchMode="singleTop" />
<activity android:name=".MyActivity$B" android:launchMode="singleTop" />
<activity android:name=".MyActivity$C" android:launchMode="singleTop" />
<activity android:name=".MyActivity$D" android:launchMode="singleTop" />
<activity android:name=".MyActivity$E" android:launchMode="singleTop" />
<activity android:name=".MyActivity$F" android:launchMode="singleTop" />
<activity android:name=".MyActivity$G" android:launchMode="singleTop" />
<activity android:name=".MyActivity$H" android:launchMode="singleTop" />
<activity android:name=".MyActivity$I" android:launchMode="singleTop" />
<activity android:name=".MyActivity$J" android:launchMode="singleTop" />
Now, whenever you need to start a new "top" instance of your Activity, you can do something like:
final Intent intent = getSequencedActivityIntent(+1);
if (intent == null) return;
intent.putExtra("dataset", dataSet);
startActivity(intent);
And when you need to go back to one of the instance in the backstack you can do:
final Intent intent = getSequencedActivityIntent(- stepBack);
if (intent == null) return;
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
You can index your activities without having to worry about handling all the chain of onActivityResults using a super Activity that you extend in all your activities
Here is an implementation (I did not test it) but if you extend this SuperActivity in all your Activities, you can call fallBackToActivity( int ) to any activity using its index and each activity now has a getIndex(). You can use it to fallback to a relative index like getIndex()-3
package sherif.android.stack.overflow;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
public class SuperActivity extends Activity {
private static String EXTRA_INDEX = "SUPER_INDEX";
private static int RESULT_FALLBACK = 0x123456;
private int index;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(getIntent()!=null) {
index = getIntent().getIntExtra(EXTRA_INDEX, -1) + 1;
}
}
protected final int getIndex() {
return index;
}
protected final void fallBackToActivity(int index) {
Intent intent = new Intent();
intent.putExtra(EXTRA_INDEX, index);
setResult(RESULT_FALLBACK, intent);
finish();
}
#Override
public void startActivityForResult(Intent intent, int requestCode) {
intent.putExtra(EXTRA_INDEX, getIndex());
super.startActivityForResult(intent, requestCode);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(resultCode == RESULT_FALLBACK) {
if(data.getIntExtra(EXTRA_INDEX, -1)!=getIndex()) {
setResult(RESULT_FALLBACK, data);
finish();
}
}
}
}
You can just keep a condition in your statement
if user chooses this item pass intent to B class
and if user chooses that item pass intent to C class
Add extra to your intent that will point the activity what to do.
For example
intent.putExtra("STATE", 1);
And get this value in onCreate of your activity.
getIntent().getExtras()
As I understand Android only targets the class of activity and not a particular instance of activity. So, I think you won't be able to do what you want by just adding some flags on Intent.
I think the easiest approach would be to implement it on your own by something like this
a) Create some singleton and have a member in it which points to instance of activity to which you want to return (as example activity B). Probably, you will have to store all activities in some list to be able to get instance of some previously launched activity.
b) Override onResume for all activities and in it do following check:
if (SomeSingleton.getTargetActivity() != null && this != SomeSingleton.getTargetActivity())
finish();
else
SomeSingleton.setTargetActivity(null);
c) As soon as you need to return from E do
SomeSingleton.setTargetActivity(B);
finish();
This will close top activity (which is E) and call onResume on activity D. It will check whether it's target. If it's not then it will close it and system will call onResume on activity C and so on.
If you want to use an abnormal approach, or some tricky once, you will have more problems later. I think you can
Define a abstract/non-abstract subclass of Activity and define everything you want. If other classes are exactly the same as the above class, so just subclass from it and do nothing more. But if the classes ( Activities ) may different from each other, you can provide abstract/non-abstract methods to define additional abilities.
So
You write a reusable codes for all activities,
You act normal so you will get good result
You can control everything specialized in your activites
You can control stack using manifest file
and more
for detailed information see below codes:
Parent Activity:
public abstract class AbstractActivity extends Activity {
AbstractActivity currentActivity;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
currentActivity = this;
someProtectedMethod();
commonMethod();
// etc...
/* event handling */
Button btn_first = (Button) findViewById(R.id.btn_first);
Button btn_second = (Button) findViewById(R.id.btn_second);
btn_first.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(currentActivity, FirstActivity.class);
currentActivity.startActivity(intent);
}
});
btn_second.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(currentActivity, SecondActivity.class);
currentActivity.startActivity(intent);
}
});
}
/** you must override it, so you can control specified things safe */
protected abstract void someProtectedMethod();
protected void commonMethod() {
Log.i("LOG", "Hello From " + getClass().getName());
}
#Override
protected void onResume() {
super.onResume();
//some statement that work in all activities to
Log.i("LOG", "On Resume: " + getClass().getName());
}
}
First Activity:
public class FirstActivity extends AbstractActivity {
#Override
protected void someProtectedMethod() {
Log.i("LOG", "Special Action From First Activity");
}
}
Second Activity:
public class SecondActivity extends AbstractActivity {
#Override
protected void someProtectedMethod() {
Log.i("LOG", "Special Action From Second Activity");
}
}
main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<Button
android:id="#+id/btn_first"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:text="Open First Activity" />
<Button
android:id="#+id/btn_second"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:text="Open Second Activity" />
</LinearLayout>
Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.activity_control"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="7" />
<application
android:icon="#drawable/ic_launcher"
android:label="#string/app_name" >
<activity
android:name=".FirstActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:label="#string/app_name" >
</activity>
</application>
</manifest>
The best and the easiest solution (so far) will be use Fragments and FragmentManager. Then tag each Fragment and use FragmentManager. Using only Activity can be very difficult to have almost the same result.
I`m working on an app that does reading and handling specific URIs from NFC tags. I have a "reader" activity (A) registered on NDEF_DISCOVERED which reads the data from the tag and than launches a "data handling" activity (B) that operates with the data.
Currently I have three tags, each with a different URI, more specifically with the same schema and path, but with different query data --> the tagID:
T-1: mySchema://gman.com/path?id=T-1
T-2: mySchema://gman.com/path?id=T-2
T-3: mySchema://gman.com/path?id=T-3
Manifest:
<activity
android:label="#string/reader_nfc"
android:name=".reader.nfc.NfcReaderActivity"
android:stateNotNeeded="true" >
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="mySchema" />
</intent-filter>
</activity>
<activity
android:label="#string/data_manager_name"
android:name=".data.handlers.DataHandlerActivity" >
</activity>
So, getting to the problem. When I read the data from the first tag, for example T-1, the reader activity goes normally trough the lifecycle and launches the data handling activity which does its job and shows the correct output. The same thing happens when I read from the next tag (T-2 or T-3), but when I return to the first tag I get the output produced from the last-previously scanned tag.
The log shows something like this:
the ActivityManager logs the the start of the intent with the right data (from T-1) but the lifecycle of activity A doesn`t get started, instead activity B restarts and the data from the previous intent is received and handled (by calling getIntent().getData() in activity B).
When switching between T-2 and T-3 everything works fine.
I would really appreciate if someone explaind me what is going on. I saw similar behaviour when setting singleTask launch mode, but I`m not using it.
I`m developing on API v.2.3.3, testing on Nexus-S with android version 2.3.6
Thnx!
==================================================================================
EDIT: I found a solution on my problem which fits my needs, still I have a question.
I focused on the reader activity and commented out the handling and the other stuff that was going on. Here is the code snipet:
public class NfcReaderActivity extends Activity {
private static final String TAG = "NfcReaderActivity";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
setContentView(R.layout.nfc_reader);
}
#Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart");
readAndHandleData();
}
protected void readAndHandleData() {
NdefMessage[] srcObj = readSource();
if (srcObj != null) {
Uri srcData = getSrcData(srcObj);
launchSourceManagerActivity(srcData);
} else {
Log.w(TAG, "srcObj was null!");
}
}
public NdefMessage[] readSource() {
Parcelable[] rawMsgs = getIntent().getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
NdefMessage[] ndefMsgs = null;
// store NdefMessage-s from rawMsgs in ndefMsgs
return ndefMsgs ;
}
public void launchSourceManagerActivity(Uri srcData) {
// launches DataHandlerActivity with srcData
}
public Uri getSrcData(NdefMessage[] src) {
// returns the data from the tag rapresented as Uri
}
}
After doing this I got some extra logs (don't know why, but I guess this is not that important), and saw that when I'm returning to the first tag, the activity gets restarted (onRestart() is called), whereas in the other two cases, when scanning the second and the third tag, the activity is recreated (onCreate() is called).
When onRestart() is called and I retrieve the data from the intent (readSource method), the getIntent() method returns the same intent received when scanning the previous tag.
I just recently started developing on Android and I`m not very familiar with the concepts, so maybe this is the core problem here ;). I tried to figure this out but I just can't get to a logical explanation. If someone could explain me the workflow here I would be really grateful.
However, this is how I resolved the issue...since the reader activity can act as a singleton I've set the launch mode as singleTask
<activity
android:label="#string/reader_nfc"
android:name=".reader.nfc.NfcReaderActivity"
android:stateNotNeeded="true"
android:launchMode="singleTask" >
and done the following changes in NfcReaderActivity:
public class NfcReaderActivity extends Activity {
private static final String TAG = "NfcReaderActivity";
/**
* override onNewIntent method and store the new intent as the current intent
*/
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, "onNewIntent");
// set the intent as the current intent, so new data (EXTRA_NDEF_MESSAGES) can
//be accessed when calling getIntent() in readSource method
setIntent(intent);
}
This works fine for me, but I would still like to understand what exactly was happening, so any useful (of course ;) ) comments are welcome.
You might want to read up on the activity lifecycle and stack:
http://developer.android.com/guide/topics/fundamentals/tasks-and-back-stack.html
I've read a few articles here (and other places) that describe how to dynamically choose which activity to show when launching an app. Below is my code:
AndroidManifest.xml
<activity android:name=".StartupActivity"
android:theme="#android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
StartupActivity.java
public class StartupActivity extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Intent intent;
if (RandomClass.getSomeStaticBoolean())
{
intent = new Intent(this, ActivityOften.class);
}
else
{
intent = new Intent(this, ActivityRare.class);
}
startActivity(intent);
finish();
}
}
Both ActivityOften and ActivityRare are declared in the manifest (without the launcher category of course) and extend ListActivity and Activity respectively. 99% of the time the 1st activity to get shown is ActivityOften based on RandomClass.getSomeStaticBoolean().
So launching my app from the icon for the 1st time I break inside the StartupActivity.onCreate. The choice is properly made. But then any subsequent attempts to launch the app (from a shortcut or the apps menu) show the ActivityOften again. No further breaks occur inside the StartupActivity class. Despite the fact that I know that RandomClass.getSomeStaticBoolean() has changed value and that ActivityRare should appear, the 1st activity keeps popping up.
Any ideas?
Thanks, Merci, Gracias, Danke, Grazie!
Sean
It is happening because your application activity is loaded from the history stack.
Set android:noHistory=true in the manifest for both ActivityOften and ActivityRare. That should solve your problem.
Just as a suggestion, you could just have one activity instead of three by choosing the content View dynamically. i.e.
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (RandomClass.getSomeStaticBoolean())
{
setContentView(R.layout.Often);
// Set up often ....
}
else
{
setContentView(R.layout.Rare);
// Set up rare ....
}
}
This would mean that you would have to write setup code both views in on activity, which can get a bit messy.