13

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..

Vasily Kabunov
  • 6,511
  • 13
  • 49
  • 53
katit
  • 17,375
  • 35
  • 128
  • 256

6 Answers6

15

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.

riwnodennyk
  • 8,140
  • 4
  • 35
  • 37
  • 1
    question is from 2011 when fragments wasnt even introduced :) – katit Mar 14 '14 at 18:51
  • 2
    Although it is good, you are assuming that the context is FragmentActivity which may be wrong in most of the cases. – tasomaniac Sep 08 '15 at 07:38
  • That's a pretty neat solution !!! If you min-API is 11 or above, you can easily do this with normal fragments. – Flo Nov 14 '15 at 06:11
  • 3
    This throw me exception with Fragment must be static and not in anonymous class :/. But looks as pretty cool workaround :). – mtrakal Aug 30 '16 at 12:11
9

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.

Florin
  • 91
  • 1
  • 2
  • I ended up using events. My custom views raise "NeedImage" event and parent Activity processes it and passing back into custom view. Backwards, I know.. But at least it works this way – katit Nov 27 '11 at 18:40
  • i 100% agree with, this design is a bad idea. and ive been trying to get past that for a while no luck yet. – Ofek Ron Sep 10 '12 at 16:12
  • this is why fragments were created. see this answer for more details http://stackoverflow.com/a/14912608/909956 – numan salati Feb 16 '13 at 16:48
2

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);
jazzgil
  • 2,250
  • 2
  • 19
  • 20
2

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.

Vasily Kabunov
  • 6,511
  • 13
  • 49
  • 53
Gregory
  • 4,384
  • 1
  • 25
  • 21
  • I realized that.. Do you have sample code where I can see how to setup my custom events inside view so I can setup listener inside Activity? – katit Jul 05 '11 at 17:51
  • Once you setup your views, you can use findViewById(int id) in your activity to get a reference to your view. Just cast it to the correct type, and then call setOnClickListener on it. http://developer.android.com/reference/android/view/View.html – Gregory Jul 05 '11 at 18:09
  • It's not as simple. View is compound control. With 5+ buttons and text/images Simple OnClick won't work. I have to do some kind of delegate inside View's code and fire it off when button click detected inside. Then this custom event need to be caught in main Activity. I never wrote such code in Java – katit Jul 05 '11 at 18:30
  • the findViewById method can find any view displayed with a given ID. If you give your button the id R.whatever.button, then using (Button)findViewById(R.whatever.button) will return that button. If you post some of your code I'll add the needed code to make it work. – Gregory Jul 05 '11 at 20:54
  • I see what you saying but still won't work :) I have multiple instances of my View on same activity. Not sure which button in which instance findViewById will return – katit Jul 05 '11 at 21:14
  • How are you creating those views ? Using a ListView and an adapter ? If that's the case, you can put the OnClickListeners from the adapter's getView method. – Gregory Jul 05 '11 at 21:16
  • No. I inflate layoyt inside my class that implements View. It's too long of a code to publish but what you saying is workaround. Ideally I need to catch OnClick INSIDE my custom view and then bubble it up to main Activity – katit Jul 05 '11 at 22:02
  • Look like I lost my last message :/ I would use a setActivity method on the custom view to tell the views which activity there is (you do something equivalent using the context and casting it to Activity). I would then use setTag on the custom view to know which object if associated with the view. I'm still not sure what the problem is, can't you just keep a reference to that view when you call startActivityForResult (like, add a new method with that name that takes an extra View parameter), store that reference in a field, and then onResult you know which view was concerned ? – Gregory Jul 06 '11 at 05:36
1

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.

Sagan
  • 112
  • 1
  • 7
1

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.

inazaruk
  • 74,247
  • 24
  • 188
  • 156