I'm experimenting with receiving Share intents, but I've come across a situation that I can't wrap my head around. Currently my app shows up as a Share option in other apps, and my Activity receives such these intents just fine. However, I'm having a problem with passing off this intent to a Fragment that I want to handle it. Namely, the Fragment isn't being loaded in time for my Activity to pass it off.
Here's what I have so far in my Activity's onCreate():
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start_screen);
if (savedInstanceState == null)
{
Log.i(TAG, "Loading new fragment");
getFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment(), "tag")
.commit();
}
PlaceholderFragment frag = (PlaceholderFragment) getFragmentManager().findFragmentByTag("tag");
Log.i(TAG, "Fragment Exists: " + (frag != null));
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
if(Intent.ACTION_SEND.equals(action) && type != null)
{
if("text/plain".equals(type))
{
//handleIntent(intent); // Doesn't do anything right now
}
}
}
And here's the log output from when I hit Share in another app and tell it to send the text to my app:
I/ActivityStartScreen﹕ Loading new fragment
I/ActivityStartScreen﹕ Fragment Exists: false
I/ActivityStartScreen﹕ Handling Intent
I/PlaceholderFragment﹕ Loading Fragment View
As you can see, the Activity wants to hand off the intent before the Fragment is ready, even though I've added a Fragment to the layout before handling the intent. Is part of the problem the fact that the FragmentTransaction needs time to commit? That's the only reason I can imagine the null-check would fail...
What do I do in this situation so that I can pass off the intent to the Fragment once it's ready to go?
Related
I got an assignment to create an application. At the Splash screen stage, it is instructed to get a list from SQLite while in a Splash screen, and pass it to a Fragment (I did so by using an Intent).
My question is why not get it from the Fragment rather than passing it from the Splash screen to the Main Activity, and from there to the Fragment? It may seem unnecessary if it wasn't for some reason that's unknown to me.
When looking for information on this question I couldn't find anything. I guess it didn't come up previously, or at least I couldn't find the phrasing that was previously used.
The method that gets the list and passes it to the MainActivity:
private void toMainActivity(ArrayList<Movie> moviesList) {
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
intent.putParcelableArrayListExtra("moviesList", moviesList);
startActivity(intent);
finish();
}
At MainActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayList<Movie> moviesList = this.getIntent().getParcelableArrayListExtra("moviesList");
Bundle listBundle = new Bundle();
listBundle.putParcelableArrayList("moviesList", moviesList);
Fragment mlf = new MoviesListFragment();
mlf.setArguments(listBundle);
ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.fragments_container, mlf);
ft.commit();
}
At the Fragment:
Bundle listBundle = getArguments();
if (listBundle != null) {
moviesList = getArguments().getParcelableArrayList("moviesList");
moviesAdapter.attachMoviesList(moviesList);
}
That's usually a matter of making sure you're working on the same instance of something. When passing something in a Bundle you're securing that the next screen will work on the same instance of the passed data that the previous screen was.
Whether it's better or worse than querying a db every time is a case-by-case thing. Sometimes you want to maintain data "continuity" between screens and sometimes you want to get the newest copy of the data (which could've been modified asynchronously).
In my FirstActivity, user will log in. If the user exists in the database, it is loaded and should be "passed" to the SecondActivityFragment which is within the SecondActivity. The need is to check whether the user is with incomplete register, if so, the toolbar will display a warning menu item telling it to complete the registration.
┌FirstActivity
├─SecondActivity
└──SecondActivityFragment
Every tutorial that I see showing how pass data through Activity and Fragment talking about replace fragments and so on, I think that's not my case.
I created newInstance() on my SecondActivityFragment but I'm kinda lost.
public static SecondActivityFragment newInstance(User user) {
Bundle args = new Bundle();
args.putSerializable("user", user);
SecondActivityFragment fragment = new SecondActivityFragment();
fragment.setArguments(args);
return fragment;
}
And when user clicks in login button
if (userExists()) {
userManager = new UserManager();
User user = userManager.getByEmailPwd(editEmail.getText().toString(), editPwd.getText().toString());
Intent secondActivity = new Intent(getContext(), SecondActivity.class);
SecondActivityFragment.newInstance(user);
startActivity(secondActivity);
}
Try to put user into your secondActivity intent.
Then in the SecondActivity's onCreate method get the user class using getIntent().getSerializable() and create an instance of SecondActivityFragment.
Calling
SecondActivityFragment.newInstance(user);
that way, will not cause any effects in what will got presented.
If you want to present the fragment in the context of Second activity, consider passing the data that the fragment need to know to the Second activity - it should be sth like:
secondActivity.putSerializable("user", user)
Then in SecondActivity's onCreate, or in other method, you have to replace fragment being displayed, for your SecondActivityFragment instance:
User user = null;
final Bundle args = getIntent().getExtras();
if(args.getSerializable("user") instanceof User){
user = (User)args.getSerializable("user");
}
if(user != null){
Fragment secondActivityFragment = SecondActivityFragment.newInstance(user);
FragmentMenager fragmentMenager = getFragmentMenager();
FragmentTransaction fragmentTransaction = fragmentMenager.beginTransaction();
fragmentTransaction.replace(R.id.frame_for_your_fragment, secondActivityFragment);
}
I'm getting an NPE when I start an activity in my application. It doesn't happen right away when I boot my phone and start debugging on it. After several dozen new builds are pushed to my phone it eventually starts crashing with this error every single time. I can remedy it temporarily by opening a few other activities first before I activate the errant one.
Any ideas what could cause this? It's a somewhat complicated Activity with a DrawerLayout, and a fragment that contains a SwipeRefreshLayout with a ListView.
Exception
java.lang.RuntimeException: Unable to start activity ComponentInfo{}: java.lang.NullPointerException: Attempt to read from field 'boolean android.support.v4.app.BackStackRecord.mAddToBackStack' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2298)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2360)
at android.app.ActivityThread.access$800(ActivityThread.java:144)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1279)
Activity Code
public class TastingsActivity extends BaseActivity implements TastingListFragment.Callbacks {
public static final String TAG = TastingsActivity.class.getSimpleName();
private TastingListFragment mTastingListFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tastings);
//create fragment
// Check that the activity is using the layout version with
// the fragment_container FrameLayout
if (findViewById(R.id.container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create a new Fragment to be placed in the activity layout
TastingListFragment fragment = new TastingListFragment();
// In case this activity was started with special instructions from an
// Intent, pass the Intent's extras to the fragment as arguments
fragment.setArguments(getIntent().getExtras());
// Add the fragment to the 'fragment_container' FrameLayout
getSupportFragmentManager().beginTransaction()
.add(R.id.container, fragment).commit();
}
}
#Override
protected int getSelfNavDrawerItem() {
return NAVDRAWER_ITEM_TASTINGS;
}
//================================================================================
// Handlers
//================================================================================
#Override
public void onTastingSelected(Tasting tasting) {
Intent intent = new Intent(this, TastingActivity.class);
intent.putExtra(TastingDetailsFragment.EXTRA_TASTING_ID, tasting.getId());
startActivity(intent);
}
}
This might have already answered but I am still troubling with a function like this. Let's say I have activity A and activity B. B holds a viewpager with several fragments in it. I would like to call a function in the fragment held by activity B from activity A.
I used callbacks many times to communicate between activites and fragments but every single time it was only the fragment and its holder activity. I do not want to make a static method (the callback listener cannot be static anyway) so it causes a headache for me. The simple static solution to make a static method in the fragment and have it called from the other actually works very well, but I am not sure if it was a good idea as I need to change several things static.
So communicating between Activity B and its fragments is ok, but I cannot call this method in Activity A.
Activity B:
public class ActivityB extends FragmentActivity implements Fragment1.OnWhateverListener
{
...
#Override
public void onWhateverSelected(int position) {
//stuff, here I can call any function in Fragment 1
}
}
The following code snippet is a wrong solution (doesnt even work) but makes a better picture what I would like to do.
Activity A:
ActivityB ab = new ActivityB ();
ab.onWhateverSelected(number);
So how can I do this?
Thank you!
EDIT
Activity A: the method I call
Bundle args = new Bundle();
args.putString("ID", id); // the data to send
Intent frag_args = new Intent(Intent.ACTION_VIEW);
frag_args.setClass(this, MainActivity.class);
frag_args.putExtra("args", args);
startActivity(frag_args);
Activity B:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
...
processIntent(getIntent()); //last line of onCreate, always gets called here
}
#Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processIntent(intent); // this never gets called here only in OnCreate
}
private void processIntent(Intent intent) {
Bundle args = intent.getBundleExtra("args");
if (args != null) { // check if ActivityB is started to pass data to fragments
String id = args.getString("ID");
Log.i("ID_FROM", "id: " + id); //works well
if (id != null) {
List<Fragment> fragments = new ArrayList<Fragment>();
fragments = getSupportFragmentManager().getFragments();
//NULLPOINTER for the following line
FragmentMainDiscover fr = (FragmentMainDiscover) fragments.get(0);
fr.RefreshHoverView(id);
}
}
}
You are right to stay away from statics. Way too risky, for visual objects that may or may not be on screen.
I would recommend going through activity B, since it is the parent of your target fragment. Create an Intent that starts activity B, and include an intent extra that tells activity B what it should do to the target fragment. Then activity B can make sure that the fragment is showing, and pass the information on to it.
One other idea to pass the info to the fragment is to use setArguments, rather than direct calls. This is a nice approach because Android will restore the arguments automatically if the activity and its fragments are removed from memory.
Does this make sense? Do you want the code?
EDIT
To use arguments, you still need to have activity A go through activity B. This is because activity A doesn't know if activity B, and all its fragments, is running unless it sends it an Intent. But you can include data targeted for the fragments, by putting them inside the intent. Like this:
public class ActivityA extends Activity {
public static final String KEY_FRAG = "frag"; // tells activity which fragment gets the args
public static final String KEY_ARGS = "args";
public static final String KEY_MY_PROPERTY = "myProperty";
public void foo() {
Bundle args = new Bundle();
args.putString(KEY_FRAG, "frag1Tag"); // which fragment gets the data
args.putCharSequence(KEY_MY_PROPERTY, "someValue"); // the data to send
// Send data via an Intent, to make sure ActivityB is running
Intent frag_args = new Intent(Intent.ACTION_VIEW);
frag_args.setClass(this, ActivityB.class);
frag_args.putExtra(KEY_ARGS, args);
startActivity(frag_args);
}
}
public class ActivityB extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//TODO configure activity including fragments
processIntent(getIntent()); // this call is in case ActivityB was not yet running
}
#Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processIntent(intent); // this call is in case ActivityB was already running
}
private void processIntent(Intent intent) {
Bundle args = intent.getBundleExtra(ActivityA.KEY_ARGS);
if (args != null) { // check if ActivityB is started to pass data to fragments
String fragTag = args.getString(ActivityA.KEY_FRAG);
if (fragTag != null) {
Fragment frag = getSupportFragmentManager().findFragmentByTag(fragTag);
frag.setArguments(args);
//TODO either show the fragment, or call a method on it to let it know it has new arguments
}
}
}
}
public class Fragment1 extends Fragment {
public static final String TAG = "frag1Tag";
#Override
public void onResume() {
super.onResume();
Bundle args = getArguments();
String value = args.getString(ActivityA.KEY_MY_PROPERTY);
...
}
}
I am attempting to expand my application by adding a TabHost and some tabs to navigate extra features. The current app basically searches a database. The current application workflow:
App loads to a login screen
User logs in
User gets a search form and inputs data, presses "search"
Search loads a list activity of results...
With the new tabs, there is a separate tab for searching. I want all the seach activities to remain inside that tab group. So I've created an activity group to handle all of these:
public class searchGroup extends ActivityGroup {
public static searchGroup group;
private ArrayList<View> history;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.history = new ArrayList<View>();
group = this;
View view = getLocalActivityManager().startActivity("search", new Intent(this,search.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)).getDecorView();
replaceView(view);
}
public void replaceView(View v) {
history.add(v);
setContentView(v);
}
public void back() {
if(history.size() > 0) {
history.remove(history.size()-1);
setContentView(history.get(history.size()-1));
}else {
finish();
}
}
#Override
public void onBackPressed() {
searchGroup.group.back();
return;
}
}
In my search activity's Search button onClickListener:
view = searchGroup.group.getLocalActivityManager().startActivity("search_results",new Intent(search.this, search_results.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)).getDecorView();
searchGroup.group.replaceView(view);
This is where I get the crash:
02-11 13:43:49.481: E/AndroidRuntime(1165): java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.myApp/com.myApp.search_results}:
android.view.WindowManager$BadTokenException: Unable to add window --
token android.app.LocalActivityManager$LocalActivityRecord#40543360 is
not valid; is your activity running?
However, if I uncomment a line from the search_result activity's onCreate:
new LoadSearches().execute();
no crash, but I get nothing obviously. LoadSearches() is an AsyncTask that does the heavy lifting of going out to the server and running the search string and then populating the returned data into the ListActivity in onPostExecute().
I don't quite understand why its crashing here and not normally when I switch activities. How should I tackle this? Is there a better way? I've read a little bit about Fragments but haven't done anything with it yet.
I have decided, after much pulling my hair out, to go with fragments. Some resources I found useful for converting my existing app to use Fragments and tabs:
Fragments in Android 2.2.1, 2.3, 2.0. Is this possible?
http://www.e-nature.ch/tech/?p=55
http://thepseudocoder.wordpress.com/2011/10/04/android-tabs-the-fragment-way/
I also had an issue with pass data between my activities. The way to pass data between activities using an intent/bundle doesn't really work the same but can modified slightly and still work.
The old way (passing data from Activity1 to Activity2):
Activity1
Intent myIntent = new Intent(search.this, search_results.class);
Bundle b = new Bundle();
b.putString("SEARCHSTRING", strSearch);
myIntent.putExtras(b);
startActivityForResult(myIntent, 0);
Activity2
Bundle b = getIntent().getExtras();
strSearch = b.getString("SEARCHSTRING");
Using fragments I had to create an initializer for Activity2:
public search_results newInstance(String strSearch){
search_results f = new search_results();
Bundle b = new Bundle();
b.putString("SEARCHSTRING", strSearch);
f.setArguments(b);
return f;
}
using this, the new method using Fragments:
Avtivity1
Fragment newFragment = new search_results().newInstance(strSearch);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.realtabcontent, newFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
Activity2 (onCreateView)
Bundle b = getArguments();
strSearch = b.getString("SEARCHSTRING");
I hope this helps someone as it was difficult for me to find all this information in one spot.