One of the common problem we always meet in the world of Fragment is: although we could callstartActivityForResult directly from Nested Fragment but it appears that onActivityResultwould never been called which brought a lot of trouble to handle Activity Result from Nested Fragment.

Why does this happen? That's because Fragment is not first designed to be nested. Once its capability was expanded, the architecture behind Fragment couldn't cover all the case. And we developers have to handle the problem case by case by ourselves.

But don't worry, we already have a sustainable and robust workaround for this problem. Ok, let's start !

Architecture behind Fragment's startActivityForResult

Although we could call startActivityForResult directly from Fragment but actually mechanic behind are all handled by Activity. Once you call startActivityForResult from a Fragment,requestCode will be changed to attach Fragment's identity to the code. That will let Activity be able to track back that who send this request once result is received.

Once Activity was navigated back, the result will be sent to Activity's onActivityResult with the modified requestCode which will be decoded to original requestCode + Fragment's identity. After that, Activity will send the Activity Result to that Fragment through onActivityResult. And it's all done.

The problem is: 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.

The Solution

This behavior is one of the most popular issue in town. We could found a lot of thread related to this in stackoverflow. There are a lot of workaround provided by people there. Anyway none of them is sustainable enough to be used in any case (at least all of those that I discovered). So we spend a day research all the mechanic behind and try to find the way to cover all the cases available. And finally we found one!

The problem, as described above, is the request could be sent from nested fragment but couldn't be received properly. Thus there is no need to do those things in Fragment. Let them be all done in Activity level.

So we will call getActivity().startActivityForResult(...) from Fragment instead of just startActivityResult(...)  from now on. Like this:

1
2
3
// In Fragment
Intent intent = new Intent(getActivity(), SecondActivity.class);
getActivity().startActivityForResult(intent, 12345);

As a result, all of the result received will be handled at the single place: onActivityResult of the Activity that Fragment is placed on.

Question is how to send the Activity Result to Fragment?

Due to the fact that we couldn't directly communicate with all of the nested fragment in the normal way, or at least in the easy way. And another fact is, every Fragment knows that which requestCode it has to handled since it is also the one that call startActivityForResult. So we choose the way to"broadcast to every single Fragment that is active at time. And let those Fragments check requestCode and do what they want."

Talk about broadcasting, LocalBroadcastManager could do the job but the mechanic is the way too old. I choose another alternative, an EventBus, which has a lot of choices out there. The one that I chose was Otto from square. It is really good at performance and robustness.

First of all, add a following line in build.gradle to include Otto to our project:

1
2
3
dependencies {
  compile 'com.squareup:otto:1.3.6'
}

In the Otto way, let's create a Bus Event as a package carry those Activity Result values.

ActivityResultEvent.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import android.content.Intent;
 
/**
 * Created by nuuneoi on 3/12/2015.
 */
public class ActivityResultEvent {
 
    private int requestCode;
    private int resultCode;
    private Intent data;
 
    public ActivityResultEvent(int requestCode, int resultCode, Intent data) {
        this.requestCode = requestCode;
        this.resultCode = resultCode;
        this.data = data;
    }
 
    public int getRequestCode() {
        return requestCode;
    }
 
    public void setRequestCode(int requestCode) {
        this.requestCode = requestCode;
    }
 
    public int getResultCode() {
        return resultCode;
    }
 
    public void setResultCode(int resultCode) {
        this.resultCode = resultCode;
    }
 
    public Intent getData() {
        return data;
    }
 
    public void setData(Intent data) {
        this.data = data;
    }
}

And of course, also create a Singleton of Event Bus which will be used to send a package from an Activity to all of active Fragments.

ActivityResultBus.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import android.os.Handler;
import android.os.Looper;
 
import com.squareup.otto.Bus;
 
/**
 * Created by nuuneoi on 3/12/2015.
 */
public class ActivityResultBus extends Bus {
 
    private static ActivityResultBus instance;
 
    public static ActivityResultBus getInstance() {
        if (instance == null)
            instance = new ActivityResultBus();
        return instance;
    }
 
    private Handler mHandler = new Handler(Looper.getMainLooper());
 
    public void postQueue(final Object obj) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                ActivityResultBus.getInstance().post(obj);
            }
        });
    }
 
}

You may notice that I also create a custom method named postQueue in the bus object. This one is used to send a package into the bus. And the reason why we have to do it this way is because we have to delay a package sending a little bit since at the moment that Activitiy's onActivityResult has been called, the Fragment is not become active yet. So we need to let Handler send those commands to the queue of Main Thread with handler.post(...) like coded above.

And then we will override onActivityResult on Activity and add a following line to send the package to the bus once the result is received.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MainActivity extends ActionBarActivity {
 
    ...
 
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        ActivityResultBus.getInstance().postQueue(
                    new ActivityResultEvent(requestCode, resultCode, data));
    }
 
    ...
 
}

In Fragment part, we need to listen to the package sent from Activity. We could do it easily in Otto way like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class BodyFragment extends Fragment {
 
    ...
 
    @Override
    public void onStart() {
        super.onStart();
        ActivityResultBus.getInstance().register(mActivityResultSubscriber);
    }
 
    @Override
    public void onStop() {
        super.onStop();
        ActivityResultBus.getInstance().unregister(mActivityResultSubscriber);
    }
 
    private Object mActivityResultSubscriber = new Object() {
        @Subscribe
        public void onActivityResultReceived(ActivityResultEvent event) {
            int requestCode = event.getRequestCode();
            int resultCode = event.getResultCode();
            Intent data = event.getData();
            onActivityResult(requestCode, resultCode, data);
        }
    };
 
    ...
 
}

That's all. Fragment's onActivityResult will be called from now on ! You can now just simply override onActivityResult, check the requestCode and do what you want.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class BodyFragment extends Fragment {
 
    ...
 
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        // Don't forget to check requestCode before continuing your job
        if (requestCode == 12345) {
            // Do your job
            tvResult.setText("Result Code = " + resultCode);
        }
    }
 
    ...
 
}

With this solution, it could be applied for any single fragment whether it is nested or not. And yes, it also covers all the case! Moreover, the codes are also nice and clean.

Limitation

There is just only one limitation. Don't use the same requestCode in different Fragment. As you can see, every single Fragment that is active at time will be receive the package. If you use the same requestCode in different Fragment, it may delivers the wrong outcome. Except that you intend to do it, you can.

Make it easy with StatedFragment

Good news! The code we described in this article are already included in our StatedFragment in version 0.9.3 and above. You could now use it easily like this:

Add a dependency in build.gradle

1
2
3
dependencies {
    compile 'com.inthecheesefactory.thecheeselibrary:stated-fragment-support-v4:0.9.3'
}

In case you use Fragment from android.app.*, please add the following instead.

1
2
3
dependencies {
    compile 'com.inthecheesefactory.thecheeselibrary:stated-fragment:0.9.3'
}

To enable it, just simply override method onActivityResult in the Activity and add a following line:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MainActivity extends ActionBarActivity {
 
    ...
 
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        ActivityResultBus.getInstance().postQueue(
                    new ActivityResultEvent(requestCode, resultCode, data));
    }
 
    ...
 
}

For Fragment, you could simple extends StatedFragmentonActivityResult will be now useful.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BodyFragment extends StatedFragment {
 
    ...
 
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        // Add your code here
        Toast.makeText(getActivity(), "Fragment Got it: " + requestCode + ", " + resultCode, Toast.LENGTH_SHORT).show();
    }
 
    ...
 
}

As I said. Easy, huh?

Hope that this article is helpful to you all. Best wishes to you all =)

How to make onActivityResult get called on Nested Fragment的更多相关文章

  1. Android Fragment使用(二) 嵌套Fragments (Nested Fragments) 的使用及常见错误

    嵌套Fragment的使用及常见错误 嵌套Fragments (Nested Fragments), 是在Fragment内部又添加Fragment. 使用时, 主要要依靠宿主Fragment的 ge ...

  2. Android的Fragment中onActivityResult不被调用

    1.检查该Fragment所属的Activity中,是否重写了onActivityResult方法. 2.检查Fragment中的startActivityForResult的调用方式. 请确保不要使 ...

  3. android onActivityResult的执行

    1.如果activity中重写了onActivityResult函数,同时添加在该activity的fragment也重写了onActivtyResult函数,那么会执行Activity的onActi ...

  4. 解决不走onActivityResult方法

    最近在开发公司项目,在使用startActivityForResult关联俩个Activity中,发现A跳转到B,B设置setResult之后,A没有执行onActivityResult,查找一下,发 ...

  5. Android解决Fragment多层嵌套时onActivityResult无法正确回调的问题

    前言: Fragment也可以使用startActivityForResult方法去打开一个Activity,然后在其onActivityResult方法中处理结果,可是当Fragment嵌套的时候, ...

  6. android中如何用代码来关闭打开的相机

    场景描述: 比如你再应用中打开了系统相机,然后需要在几分钟后自动关闭这个系统相机(不是手动关闭) 1.在activityA中利用startActivityForResult(intent,reques ...

  7. Android Fragment使用(一) 基础篇 温故知新

    Fragment使用的基本知识点总结, 包括Fragment的添加, 参数传递和通信, 生命周期和各种操作. Fragment使用基础 Fragment添加 方法一: 布局里的标签 标识符: tag, ...

  8. 浅谈Android Fragment嵌套使用存在的一些BUG以及解决方法

    时间 2014-03-18 18:00:55 eoe博客 原文  http://my.eoe.cn/916054/archive/24053.html 主题 安卓开发 自从Android3.0引入了F ...

  9. fragment的一些bug

    自从Android3.0引入了Fragment之后,使用Activity去嵌套一些Fragment的做法也变得更加流行,这确实是 Fragment带来的一些优点,比如说:Fragment可以使你能够将 ...

随机推荐

  1. 2015 UESTC Training for Search Algorithm & String - M - Palindromic String【Manacher回文串】

    O(n)的复杂度求回文串:Manacher算法 定义一个回文值,字符串S是K重回文串,当且仅当S是回文串,且其长度为⌊N/2⌋的前缀和长度为⌊N/2⌋的后缀是K−1重回文串 现在给一个2*10^6长度 ...

  2. HTML5之兴趣爱好

  3. Python进阶:函数式编程实例(附代码)

    Python进阶:函数式编程实例(附代码) 上篇文章"几个小例子告诉你, 一行Python代码能干哪些事 -- 知乎专栏"中用到了一些列表解析.生成器.map.filter.lam ...

  4. 线程技术 ☞ Future模式

    线程技术可以让我们的程序同时做多件事情,线程的工作模式有很多,常见的一种模式就是处理网站的并发,今天我来说说线程另一种很常见的模式,这个模式和前端里的ajax类似:浏览器一个主线程执行javascri ...

  5. C#解leetcode 106. Construct Binary Tree from Inorder and Postorder Traversal

    Given inorder and postorder traversal of a tree, construct the binary tree. Note:You may assume that ...

  6. C#解leetcode:119. Pascal's Triangle II

    题目是: Given an index k, return the kth row of the Pascal's triangle. For example, given k = 3,Return  ...

  7. 5JS树形结构菜单和jQuery版

    第一版JS版HTML: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> & ...

  8. System.Web.HttpContext.Current.Session获取值出错

    在自定义类库CS文件里使用System.Web.HttpContext.Current.Session获取Session时提示错误:未将对象引用设置到对象的实例. 一般情况下通过这种方式获取Sessi ...

  9. YII框架开发一个项目的通用目录结构

    YII框架开发一个项目的通用目录结构: 3 testdrive/ 4 index.php Web 应用入口脚本文件 5 assets/ 包含公开的资源文件 6 css/ 包含 CSS 文件 7 ima ...

  10. C#设计模式-创建型模式(转)

    一.简单工厂模式 简单工厂模式Simple Factory,又称静态工厂方法模式.它是类的创建模式.是由一个工厂对象决定创建出哪一种产品类的实例,是不同的工厂方法模式的一个特殊实现. 优点: u 模式 ...