OnActivityResult when having GUI parts outside Fragment - android

I have a two pane fragment design with the detail fragment dealing with the item clicked on the leftmost, listfragment.
Since I have many different list items, they have different GUI's each one of them, I want to put the handling of the GUI parts in different separate classes.
I'm using reflections to get the code slimmed inside the ItemDetailFragment:(code below is stripped down)
*//get class gui_handler for the object and get its constructor:*
Constructor<?> ctor = mListItem.getmGuiHandler().getConstructor(View.class);
*//create an object of the gui_handler class, pass the rootView as arg:*
gui_handler_base handlerObject = (gui_handler_base) ctor.newInstance(root);
*//run setup-method containing findViewById() and more GUI related stuff:*
handlerObject.setupGUI(mListItem, getActivity());
In setupGUI:
public void setupGUI(MyListItem item, final Activity activity) {
buttonRun.setOnClickListener(new View.OnClickListener(){
public void onClick(View v) {
final String FILENAME = "XXX";
Intent I = new Intent(activity,KTActivity.class);
Bundle b = new Bundle();
b.putSerializable("FileName",FILENAME);
I.putExtras(b);
activity.startActivityForResult(I,1);
return;
}
});
#Override
public void onActivityResult(int requestCode, int resultCode,
Intent data) {
// I WANT TO END UP HERE WHEN THE ACTIVITY RETURNS;
}
I have one onActivityResult method in the gui_handler_base class and one in the ItemDetailFragment, but none of them are called.
How can I do this??

Your question is quite ambiguous. Why are you using onActivityResult? activity.startActivityForResult(I,1); why? WHy not just implement the onclicklistener class in your fragment class and be done with it? It seems like in trying to reduce code you created more headaches for yourself. Also take out numbers in your code and use constants. You have to call the intent and use the intent in the same fragment. Further more why are you extracting out the GUI functionality from the fragments? You shouldn't be. even if you are creating custom functionality, you would still deal with a fragment's functionality in the fragment.
//if you want to try this… normally when receiving a camera intent this is what you can do.
if (resultCode == Activity.RESULT_OK) {
switch (requestCode) {
case yourResultCode:
doSomethingWithYourResult;
break;
]

Related

How to make onActivityResult get called on nested fragment from viewpager

This is a famous problem of getting result from activity to a nested fragment, the activity could send the result to only the Fragment that has been attached directly to Activity but not the nested one. That's the reason why onActivityResult of nested fragment would never been called no matter what.
In my case i have one activity that contains one fragment which has a viewPager with bunch of fragments.
In one of this fragments of the viewPager i try to start a camera intent, the result returned by this intent is never sent to the last fragment, i tried severals answers that i found but none of them is robust, one of the solutions was to create an EventBus object to diffuse the resultCode from activity to all the fragments but it doesn't work for this case, any help??
I might have a working solution for you since I used to be in a very similar situation.
In short terms I stopped relying on Fragment's onActivityResult() and implemented a working logic myself.
The only onActivityResult() you should trust is the one of your Activity.
You may create an interfacelike this one
public interface ActivityResultDispatcher {
// Pass anything you want, Bundle, Intent, ecc...
// For the sake of simplicity I'm using Bundle...
public void dispatchResultData(final Bundle data);
}
and have your Fragment classes implement it.
The communication between your Activity and your primary Fragmentshould be very easy that you can do something like this (in your Activity):
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
final Bundle bundle = new Bundle();
// put data inside Bundle
this.primaryFrament.dispatchResultData(bundle);
}
Now, your primary Fragment is the one that holds the ViewPager with the other Frament classes.
So in your primary Fragment you will do:
#Override public void dispatchResultData(final Bundle data) {
for (int i = 0; i < MyViewPager.NUM_FRAGMENTS; i++) {
final ActivityResultDispatcher subFragment = (ActivityResultDispatcher) this.retrieveFragment(i);
// some fragments in ViewPager may be dynamically deleted
if (subFragment != null) {
subFragment.dispatchResultData(data);
}
}
}
The function retrieveFragment(final int position) is a very cozy piece of code I had found on the web.
An implementation for a ViewPager works like this:
public Fragment retrieveFragment(final int position) {
return this.fragmentManager.findFragmentByTag("android:switcher:" + R.id.my_pager_id + ":" + position);
}
All your active Fragment classes in the ViewPager will receive the Bundle and you can decide what to do with it.
You may pass more information to the dispatcher, such as
public void dispatchResultData(final Bundle data, final int requestCode);
and have your Fragment decide if it should answer to the function call.
You have basically built a complete fully-working onActivityResult() utility and can be sure that all of your Fragments (if instantiated) will receive the result.

Start an Activity form a Fragment

I searched in site and there were similar questions as mine but none of theme were not my answer
look at this picture:
so it is clear that i want to start CrimeActivity by sending an intent from CrimeListFragment + an extra in its intent
the book that i read for android programming its author said:
Starting an activity from a fragment works nearly the same as starting an activity from another activity.
You call the Fragment.startActivity(Intent) method, which calls the corresponding Activity
method behind the scenes
CrimeListFragment.java :
public void onListItemClick(ListView l, View v, int position, long id) {
// Get the Crime from the adapter
Crime c = ((CrimeAdapter)getListAdapter()).getItem(position);
// Start CrimeActivity
Intent i = new Intent(getActivity(), CrimeActivity.class);
i.putExtra(CrimeFragment.EXTRA_CRIME_ID, c.getId());
startActivity(i);
}
the second part is now retrieving the intent and its extra and the author said about that:
There are two ways a fragment can access data in its activity’s intent: an easy, direct shortcut and a
complex, flexible implementation. First, you are going to try out the shortcut. Then you will implement
the complex and flexible solution that involves fragment arguments.
and my problem is about the first way, the shortcut
In the shortcut, CrimeFragment will simply use the getActivity() method to access the
CrimeActivity’s intent directly. Return to CrimeFragment and add the key for the extra. Then, in
onCreate(Bundle), retrieve the extra from CrimeActivity’s intent and use it to fetch the Crime
CrimeFragment.java :
public class CrimeFragment extends Fragment {
public static final String EXTRA_CRIME_ID =
"com.bignerdranch.android.criminalintent.crime_id";
private Crime mCrime;
...
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCrime = new Crime();
UUID crimeId = (UUID)getActivity().getIntent()
.getSerializableExtra(EXTRA_CRIME_ID);
mCrime = CrimeLab.get(getActivity()).getCrime(crimeId);
}
The downside to direct retrieval
Having the fragment access the intent that belongs to the hosting activity makes for simple code.
However, it costs you the encapsulation of your fragment. CrimeFragment is no longer a reusable
building block because it expects that it will always be hosted by an activity whose Intent defines an
extra named EXTRA_CRIME_ID.
This may be a reasonable expectation on CrimeFragment’s part, but it
means that CrimeFragment, as currently written, cannot be used with
just any activity.
My question and problem is the last sentence, why this Fragment (CrimeFragment) cannot be used with just any Activity???
The author explains it. Your CrimeFragment, in its onCreate() method, gets its hosting activity (through getActivity()) and then attempts to get an UUID from the Intent used to start that Activity.
This means that any activity containing your CrimeFragment now has to obey this rule, i.e. its intent should have (in it) an extra defined by the name EXTRA_CRIME_ID. If that activity does not comply, you'll see an exception being thrown in CrimeFragment's onCreate().
Try having this fragment in a new activity created by yourself to see what happens.
retrieval in onActivityCreated()
#Override
public void onActivityCreated(Bundle savedInstanceState) {
if (savedInstanceState != null) {
....
}
else {
UUID crimeId = (UUID)getActivity().getIntent().getSerializableExtra(EXTRA_CRIME_ID);
}
}

Android - How to retrieve data from activity

I am new to android so please excuse the newbie question. I have a game I am trying to port from an old Java applet to android. My goal is to get this functional and then post an article on a site like CodeProject (or a better one if there are ones more appropriate). The idea is to show that a person brand new to android development can create an app in a reasonable amount of time.
I am making some progress but have run into a problem. I have the main activity in which the user interacts with. I then created a menu item that in turn starts a second activity (call it child) with a modest number of checkbox's, seekbar's etc to fill in parameters. I can successfully pass the class containing all the options from main to child. But I cannot get the child to pass this data back to the main.
First here is my main code that starts the child activity:
public void addBalls()
{
Intent myIntent = new Intent(this, GameOptions.class);
Bundle b = new Bundle();
b.putSerializable("options", gameParams);
myIntent.putExtras(b);
startActivityForResult(myIntent,STATIC_OPTIONS_VALUE);
}
The data passed to the child (and hopefully back again) is:
public class GameOptionParams implements Serializable
{
private static final long serialVersionUID = 1L;
public int speedBarPosition;
public int vgravityBarPosition;
public int mgravityBarPosition;
public int viscosityBarPosition;
public int restititionBarPosition;
public boolean trace;
public boolean collide;
public boolean mush;
public boolean wrap;
public boolean flicker;
}
And here is the expected return (again in main)
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
switch(requestCode)
{
case (STATIC_OPTIONS_VALUE) :
{
if (resultCode == Activity.RESULT_OK)
{
//retrieve intended options
Bundle b = data.getExtras();
gameParams = (GameOptionParams) b.getSerializable("options");
}
break;
}
}
}
The child activity successfully receives the gameParams data. It then interacts with the user to update the values and then I attempt to return it but it does not seem to get sent to main. Here is the child code in the onStop() override.
Maybe this code should not be in the onStop() override but I can't determine where else to place it.
#Override
public void onStop()
{
super.onStop();
//read widget values
gameParams.speedBarPosition = speedBar.GetPosition();
gameParams.vgravityBarPosition = vgravityBar.GetPosition();
gameParams.mgravityBarPosition = mgravityBar.GetPosition();
gameParams.viscosityBarPosition = viscosityBar.GetPosition();
gameParams.restititionBarPosition = restititionBar.GetPosition();
//todo save to persistent
Intent resultIntent = new Intent(this, TiltBall2ImpactActivity.class);
Bundle b = new Bundle();
b.putSerializable("options", gameParams);
resultIntent.putExtras(b);
setResult(Activity.RESULT_OK, resultIntent);
}
Back in the main onActivityResult override I always see requestCode=0, resultCode=0, data=null. I assume this is a typical newbie problem, I have been reading the sdk documentation, user forums etc and have come close to a solution but just not quite there yet. Any help would be appreciated.
Since this is sort of a setting menu for the game, I assume you are going to need these values for more than one activity. If so you extend the android.app.Application class.
In that class you can create attributes to hold your values. In any activity you can call
MyApplication myApp = (MyApplication)getApplicationContext();
where myApp is a singleton. So you will get the values you set from another activity.
You will need to add this code to your application tag in the manifest file for it to work
android:name=".MyApplication"
If you need to keep these values for next startup of the application, you need to use SharedPreferences. This is a good tutorial for that
http://saigeethamn.blogspot.com/2009/10/shared-preferences-android-developer.html
Assuming in your 'child' activity, the user has to press an 'OK' or 'Save' button then put the code to set the gameParams parameters in the button's onClick(...) handler.
Use the default constructor for instantiating the Intent, example...
Intent resultIntent = new Intent();
...then after creating the Bundle and adding gameParams to it and calling setResult(...), simply call finish() to terminate the 'child' activity. There aren't many occasions that I can think of to override onStop() and I suspect you don't want to be using it to attempt returning the Intent.

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