Refreshing my fragment not working like I thought it should - android

I have this nice method in my ListFragment I call to fill out the details of my other fragment:
private void showClientDetails(int pos) {
myCursor.moveToPosition(pos);
int clientId = myCursor.getInt(0);
if(mIsTablet) {
// Set the list item as checked
getListView().setItemChecked(mCurrentSelectedItemIndex, true);
// Get the fragment instance
ClientDetails details = (ClientDetails) getFragmentManager().findFragmentById(R.id.client_details);
// Is the current visible recipe the same as the clicked? If so, there is no need to update
if (details == null || details.getClientIndex() != mCurrentSelectedItemIndex) {
// Make new fragment instance to show the recipe
details = ClientDetails.newInstance(mCurrentSelectedItemIndex, clientId, mIsTablet);
// Replace the old fragment with the new one
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.client_details, details);
// Use a fade animation. This makes it clear that this is not a new "layer"
// above the current, but a replacement
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
}
}
It is called when the user clicks on a client in the ListFragment view:
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
mCurrentSelectedItemIndex = position;
showClientDetails(position);
}
This works great, but then another FragmentActivity can change the data that this displays so I thought this would work:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
//update the client list incase a new one is added or the name changes
if(requestCode==1899)
{
myCursor.requery();
theClients.notifyDataSetChanged();
showClientDetails(mCurrentSelectedItemIndex); //now if client was edited update their details in the details fragment
}
}
Now I know the line:
if (details == null || details.getClientIndex() != mCurrentSelectedItemIndex) {
Prevents the block of code being reached when its called in my onActivityResult. So if I remove that if statement, then things freak out and the ft.commit() has a hissy fit and gives me the error:
`07-08 16:53:31.783: ERROR/AndroidRuntime(2048): Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
So I guess what I am trying to do isn't as cut and dry as it sounds, it makes no sense to me since I can do that onListItemClicked all day and the fragment displays the details of the newly clicked client constantly very well...
I even tried this in my onActivityResult:
//simulate a click event really fast to refresh the client details
showClientDetails(0);
showClientDetails(mCurrentSelectedItemIndex);
that does nothing, is it im trying to call something from the onActivityResult which isn't a Ui thread or what not?
I do have this in my code of the ListFragment as well
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("currentListIndex", mCurrentSelectedItemIndex);
}
Is this what the error is complaining about?

The other FragmentActivity's action is performing a task which requires the Fragment to save its state via a call to onSaveInstanceState in preparation for a new instance being reconstructed. I've seen this for example when I was firing off an activity from a fragment which filled the entire screen as this resulted in the view being detached from the fragment, state needing to be saved etc.
You basically cannot call commit between onSaveInstanceState and the new instance of the fragment being recreated. See commit.
As for the solution, then either re-think to try and avoid the commit being called when it is or alternatively call commitAllowingStateLoss if you think it's OK for the UI to change unexpectedly on the user.

I think the answer is to not do any fragment transactions in the onActivityResult. I believe what happens is that when the onActivityResult is called the activity it is in hasn't yet resumed and restarted its fragments.
Use a handler to post back the function call to the activity.
handler.post(new Runnable() {
#Override
public void run() {
showClientDetails(mCurrentSelectedItemIndex);
}
});

you can do an other thing which no very good but it works.
finish (you activity)
do an intent of that same class by :
Intent mIntent = new Intent(getApplicationContext(), YouClass.class);
startActivity(mIntent);

Related

Updating Activity with OnResume after Fragment Closes

I have a fragment that adds data to my database. When I close it with dismiss(), it returns to my activity. I want to then update my recyclerView in that activity.
My understanding of the activity lifecycle is that onResume should be called correct? I have a Log in my onResume method and as far as I can tell it is not being called.
What is the better solution, and why is this not being called?
onResume
#Override
public void onResume() {
super.onResume();
Log.e("Resume", "Resuming");
}
The button click listener in my fragment. The Log here works perfectly fine.
//save button for saving workouts
mButton2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String title = mEditText.getText().toString();
String category = mSpinner.getSelectedItem().toString();
String weight = mEditText2.getText().toString();
int icon = buildIcon(category);
//pass all the data into a new object for the Recycler View to store and show also refresh
db.addWorkout(new Workout(title, weight, "8/8/8", icon));
Log.e("Database Build", "Added a new workout: " + title);
dismiss();
}
});
The Activity was never paused when you started dealing with the fragment. onResume won't get called in that scenario and that's expected lifecycle behavior.
You should consider implementing some type of callback to let the Activity know when the Fragment has closed. The android documentation has a really good explanation of how to communicate with fragments. Use the pattern in the documentation and build yourself an OnFragmentClosedListener

Showing fragment after activity fetches data

I'm fetching data in my activity that is needed by several fragments. After the data is returned, I create the fragments. I was doing this via an AsyncTask, but it led to occasional crashes if the data returned after a screen rotation or the app is backgrounded.
I read up and thought the solution to this was instead using an AsyncTaskLoader. Supposedly it won't callback if your activity's gone, so those errors should be solved. But this now crashes every time because "Can not perform this action (add fragment) inside of onLoadFinished".
How am I supposed to handle this? I don't want my fragments to each have to fetch the data, so it seems like the activity is the right place to put the code.
Thanks!
Edit 1
Here's the relevant code. I don't think the problem is with the code per-se, but more of my whole approach. The exception is pretty clear I shouldn't be creating fragments when I am. I'm just not sure how to do this otherwise.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportLoaderManager().initLoader(BREWERY_LOADER, null, this).forceLoad();
}
//================================================================================
// Loader handlers
//================================================================================
#Override
public Loader<Brewery> onCreateLoader(int id, Bundle args) {
int breweryId = getIntent().getIntExtra(EXTRA_BREWERY_ID, -1);
return new BreweryLoader(this, breweryId);
}
#Override
public void onLoadFinished(Loader<Brewery> loader, Brewery data) {
if (data != null) {
onBreweryReceived(data);
} else {
onBreweryError();
}
}
#Override
public void onLoaderReset(Loader<Brewery> loader) {
}
...
protected void onBreweryReceived(Brewery brewery) {
...
createFragments();
}
...
protected void createFragments() {
FragmentManager fm = getSupportFragmentManager();
//beers fragment
mBeersFragment = (BreweryBeersFragment)fm.findFragmentById(R.id.beersFragmentContainer);
if (mBeersFragment == null) {
mBeersFragment = new BreweryBeersFragment();
fm.beginTransaction()
.add(R.id.beersFragmentContainer, mBeersFragment)
.commit();
Bundle beersBundle = new Bundle();
beersBundle.putInt(BreweryBeersFragment.EXTRA_BREWERY_ID, mBrewery.getId());
mBeersFragment.setArguments(beersBundle);
}
}
Edit 2
My new strategy is to use an IntentService with a ResultReceiver. I null out callbacks in onPause so there's no danger of my activity being hit when it shouldn't be. This feels a lot more heavy-handed than necessary, but AsyncTask and AsyncTaskLoader neither seemed to have everything I needed. Creating fragments in those callback methods doesn't seem to bother Android either.
From the MVC (Model -- View -- Controller) viewpoint, both the Activity and its fragments are Controller, while it is Model that should be responsible for loading data. As to the View, it is defined by the layout xml, you can define custom View classes, but usually you don't.
So create a Model class. Model is responsible for what must survive a screen turn. (Likely, it will be a static singleton; note that Android can kill and re-create the process, so the singleton may get set to null.) Note that Activities use Bundles to send data to themselves in the future.

PreferenceFragment doesn't get onActivityResult call from in app billing request

I have a preference screen that presents the user with a checkbox to disable ads. When the user clicks this for the first time, they are presented with an In App Billing purchase option to disable the ads.
The issue I'm facing here is that I can't see any way to get the onActivityResult callback into the fragment.
So I have a PreferenceActivity loading a PreferenceFragment (of which I can't seem to get a reference). In App Billing requires a call to startIntentSenderForResult, which Fragments don't have, only Activities.
When I launch the purchase flow with startIntentSenderForResult, the Activity's onActivityResult gets called, but I need this in the fragment.
Because I loaded the PreferenceFragment into the PreferenceActivity with the following, I don't think I can get a reference to the Fragment to pass the call down.
#Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.layout.preferences_headers, target);
}
#Override
public Intent getIntent() {
final Intent modIntent = new Intent(super.getIntent());
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, SyncPreferencesFragment.class.getName());
modIntent.putExtra(EXTRA_NO_HEADERS, true);
return modIntent;
}
What am I missing here? I dont' want to split up all of my purchase logic, so how do I get my Fragment to get the onActivityForResult call?
The original start request must come from the Fragment in order for Android to deliver the result back to the same Fragment. If the Activity starts the request, the result doesn't inherently get handed to every Fragment that may be attached to the manager. In addition to this, you have to ensure that if onActivityResult() is overridden in the top-level Activity, that you call super.onActivityResult() as well, or the message won't get delivered to the Fragment.
The problem with IAB is your given a PendingIntent instead of a standard Intent to fire, and there is no method on Fragment to trigger the initial action even if you can move your code there. To keep things as they are, you may have to do a bit of a swizzle. One thing you could do is make use of a custom interface inside the onAttach() method of your Fragment and use it to allow the Fragment to hand itself up to the Activity. Something like:
public class SyncPreferencesActivity extends PreferenceActivity
implements SyncPreferencesFragment.OnAttachCallback {
SyncPreferencesFragment mTargetFragment;
#Override
public void onAttachSyncFragment(SyncPreferencesFragment fragment) {
mTargetFragment = fragment;
}
}
...with some additions to the corresponding Fragment...
public class SyncPreferencesFragment extends PreferenceFragment {
public interface OnAttachCallback {
public void onAttachSyncFragment(SyncPreferencesFragment fragment);
}
#Override
public void onAttach(Activity activity) {
try {
OnAttachCallback callback = (OnAttachCallback) activity;
callback.onAttachSyncFragment(this);
} catch (ClassCastException e) {
throw new ClassCastException("You Forgot to Implement the Callback!");
}
}
}
With something like this, at least you have a reference to the instance so you can forward the result or anything else that might be necessary. If you want to be super clean, you could also implement a matching "detach" that clears the reference in the Activity.
Here is a simpler way that I am using to get a reference to a Preference Fragment from a Preference Activity:
private WeakReference<GeneralPreferenceFragment> mGeneralFragment;
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
// "id" might be arbitrary and "tag" can be null
Log.d(TAG, "onAttachFragment: "+fragment.getId()+","+fragment.getTag());
// however class can be used to identify different fragments
Log.d(TAG, "onAttachFragment: "+fragment.getClass()+","+(fragment instanceof GeneralPreferenceFragment));
if(fragment instanceof GeneralPreferenceFragment) {
mGeneralFragment=new WeakReference<>((GeneralPreferenceFragment)fragment);
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Fragment f=mGeneralFragment.get();
if(f!=null) {
// can now use findPreference, etc
}
}
This method is convenient because it doesn't require the use of interfaces or modifications to the fragment classes.

startActivityForResult from a ListFragment doesn't seem to call onActivityResult

I did try the "fixed" jar here:
http://code.google.com/p/android/issues/detail?id=15394
and also reinstalled the SDK completely and neither approach still fixed the issue I have here. So is startActivityForResult just a no go from ListFragment?
Original post:
I have this ClientListView which is a ListFragment, that when the button on the action bar is clicked it takes what is selected in the ListFragment view and starts a new activity to edit the selected client (or if the other option is clicked a new client all together).
This all launches fine. The ClientListView fragment and the ClientDetailsFragment are replaced by my EditClientActivity FragmentActivity (which calls the ClientEdit fragment). This takes up the whole screen and creates a save/cancel button in the action bar.
The problem is that when the save is clicked, I cannot update my ListFragment with the newly created client or edited client. For completeness this is my calling order:
MainActivity FragmentActivity sets up the ClientListView ListFragment and the ClientDetailsActivity FragmentActivity (which has the ClientDetails fragment). Then the ClientListView ListFragment upon its new or edit client option being selected can startActivityForResult on the EditClientActivity (Which has the ClientEdit fragment in it).
The ClientEdit Fragment sets up the options menu for save cancel, once the save in the ClientEdit fragment is selected several things happen:
new client or edited client is saved to the database.
mEditListener.onEditComplete() is called. As the calling FragmentActivity EditClientActivity implements an onEditCompleteListener that i use onAttach in the ClientEdit fragment.
So then my EditClientActivity has the onEditComplete(long id) method:
public void onEditComplete(long id) {
Intent in = new Intent();
this.setResult(1, in); //just something to let the ClientListView that the client i saved refresh the list.
Toast.makeText(this.getBaseContext(), "Client Saved", Toast.LENGTH_LONG).show();
finish(); //go back to our listview and client details view
}
In my ClientListView (of type 'ListFragment') I have this:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
this.setHasOptionsMenu(true);
//which columns to put as the cursor
String[] columns = new String[] { "firstname", "lastname" };
//how to post those columns into the layout. check client_row.xml for these ids
int[] to = new int[] { R.id.client_first_name_list_label, R.id.client_last_name_list_label};
myCursor = getClientsCursor(); //this is NOT closing the database connection if it does it gets an error
theClients = new SimpleCursorAdapter(this.getListView().getContext(),
R.layout.client_row, myCursor, columns, to);
setListAdapter(theClients);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.new_client:
// TODO: add recipe
showClientEdit(-1);
return true;
case R.id.client_delete:
// TODO: delete recipe
Toast.makeText(getActivity(), "Delete Client selected", Toast.LENGTH_LONG).show();
return true;
case R.id.client_edit:
if(mCurrentSelectedItemIndex!=-1)
showClientEdit(mCurrentSelectedItemIndex);
else
Toast.makeText(getActivity(), "Select client to edit!", Toast.LENGTH_LONG).show();
return true;
case android.R.id.home:
// TODO: Handle app icon click
Toast.makeText(getActivity(), "Home icon selected", Toast.LENGTH_LONG).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
void showClientEdit(long someId)
{
..do stuff to get the right client to pass to the intent
Intent intent = new Intent(getActivity(), EditClientActivity.class);
// Send the recipe index to the new activity
intent.putExtra(EditClientActivity.SELECTED_CLIENT, clientId);
startActivityForResult(intent, Activity.RESULT_OK);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
//never gets here :(
super.onActivityResult(requestCode, resultCode, data);
Toast.makeText(this.getListView().getContext(), "Result code: " +resultCode , Toast.LENGTH_LONG).show();
if(resultCode ==1)
{
myCursor = getClientsCursor(); //not sure if i need this for the next line or not, want my list to update with newly
//added client or edited client names etc...
theClients.notifyDataSetChanged();
Toast.makeText(this.getListView().getContext(), "Data set notified!!!" , Toast.LENGTH_LONG).show();
}
}
Is the issue is that my startActivityForRestult calls the EditClientActivity from a ListFragment? As far as I can tell never calls the onActivityResult. I provided all this information to try to figure out and get a handle on how Fragment/FragmentActivities and the like are all supposed to interact with each other. I am doing it this way from things I learned poking around tutorials, the developer guide etc. I am pretty happy with my progress but at a wall now...and probably realizing the way I am doing things is just not the right way...I would love to be enlightened. This is the hardest part of android to me is managing how all these activities and views interact with each other....
Are you using the compatibility library for Fragments?
There is an issue with the Compatibility package, onActivityResult within fragments is broken. Take a look here http://code.google.com/p/android/issues/detail?id=15394 . There you can also download a jar file with the fixed version.
I understand that this question is old, but I wanted to provide a solution for those that are hitting this question like I did when they were searching for why your onActivityResult is not running when the Intent is called from inside a fragment (ListFragment or regular Fragment will have the same result).
You must call your startActivityForResult from your main Activity. So make sure to call getActivity() when you call it from inside a fragment like this:
getActivity().startActivityForResult(Intent,ACTIVITY_INT);
Freaking figured this out. It was the way I was using the Result/request code:
startActivityForResult(intent, Activity.RESULT_OK);
was not the way to start it, added another number in there instead of Activity.RESULT_OK and it worked, I must of gotten confused.

Custom View calling startActivityForResult

I created custom compound view where I incorporate functionality to take pictures.
I'm calling it like this (from view):
Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
((Activity)mContext).startActivityForResult(intent, index);
This part works good. What I don't know how to do is how do I implement onActivityResult inside my custom view?
Or should I catch this inside Activity and than re-route into my view? Doesn't look like very nice solution..
You actually can do it like this:
#Override
public void onClick(View v) {
final FragmentManager fm = ((FragmentActivity) getContext()).getSupportFragmentManager();
Fragment auxiliary = new Fragment() {
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
//DO WHATEVER YOU NEED
super.onActivityResult(requestCode, resultCode, data);
fm.beginTransaction().remove(this).commit();
}
};
fm.beginTransaction().add(auxiliary, "FRAGMENT_TAG").commit();
fm.executePendingTransactions();
auxiliary.startActivityForResult(new Intent(getContext(), ToStartActivity.class), 3333);
}
The trick is using an auxiliary temp fragment.
I'm having the same issue, as the initial question. I know that you all posted working solution, BUT, all the solutions lack one thing: encapsulation. What do I mean - If in one activity I have 10 views that should (on some event) start another activity it should be NORMAL to be able to start that new activity from the view that needs that activity. You all are trying to convince that is better to handle all new possible activites from the initial one - than why we added different logic in each view. We may want to RE-USE code, and create one custom view that can work INDEPENDENT to where we use it (work may include showing another activity to select something for example).
I know that this is not possible (or not yet), and is a clear proof that Android SDK is not ready yet to handle real big applications.
If you want an example:in any real business app that has for example, customer list (that should be a view) ,the view should be able to launch by itself addcustomer activity, edit customer activity and so on, independent from where you put that customer list view (control) - because in big apps you need to RE-use components (you may need to show the customer list control in a order product activity, in a timesheet activity and so on.).
One possible solution could be:
- start the new activity (using the view context (normally should be the parent activity).
- on the new activity closing event, either call directly a method in the calling view (depending on the case, and posibilities: either static that is handling the code that you normally would run on activityresult, either try to pass the instance of the calling view to the new activity, and do the same. In this way, you can handle your new activity, without letting the containing activity to know anything about it.
You need to catch this from your activity. The startActivityForResult is called on your activity, so it'll be the one launching the Intent and getting the result. I'd say that it's overall bad to launch it directly from the view's code. A better solution would be with a clickListener (or checkChangeListener, or whatever you want), set by your activity, and calling a method like "openImageCapture".
When the Intent returns, your activity will take care of the result and update your views as needed.
Views are there just for displaying stuff on the screen and getting user input, the activity is there to do the actual work.
Here's a static function to implementing #riwnodennyk's solution, while overcoming the Fragment must be static and not in anonymous class error:
public static void myStartActivityForResult(FragmentActivity act, Intent in, int requestCode, OnActivityResult cb) {
Fragment aux = new FragmentForResult(cb);
FragmentManager fm = act.getSupportFragmentManager();
fm.beginTransaction().add(aux, "FRAGMENT_TAG").commit();
fm.executePendingTransactions();
aux.startActivityForResult(in, requestCode);
}
public interface OnActivityResult {
void onActivityResult(int requestCode, int resultCode, Intent data);
}
#SuppressLint("ValidFragment")
public static class FragmentForResult extends Fragment {
private OnActivityResult cb;
public FragmentForResult(OnActivityResult cb) {
this.cb = cb;
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (cb != null)
cb.onActivityResult(requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
getActivity().getSupportFragmentManager().beginTransaction().remove(this).commit();
}
}
Usage example:
Intent inPhonebook = new Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Phone.CONTENT_URI);
myStartActivityForResult((FragmentActivity) getContext(),
inPhonebook, REQUEST_CODE_PICK_CONTACT, this::onContacts);
There is no way to catch onActivityResult from your view, only from Activity.
And its not safe to assume that's Context object is Activity. In general you should not rely on this fact. Even if it seems reasonable in case with views, you still should use only methods available trough Context interface. That's because your can't predict all side-effects on the Activity, when you're calling Activity specific functions.
Just make the same method inside your custom view
And inside the activitys onActivityResult call yourView.onActivityResult(...) and process the result inside your view..
Also as guys mentioned you must not always end up with Context being of Activity class. Usually when it is from inflated view.
But if you construct your view only in code and always use the activity instance you are good.

Categories

Resources