Is there a method that works like start fragment for result?

Viewed 75264

I currently have a fragment in an overlay. This is for signing in to the service. In the phone app, each of the steps I want to show in the overlay are their own screens and activities. There are 3 parts of the sign-in process and each had their own activity that was called with startActivityForResult().

Now I want to do the same thing using fragments and an overlay. The overlay will show a fragment corresponding to each activity. The problem is that these fragments are hosted in an activity in the Honeycomb API. I can get the first fragment working, but then I need to startActivityForResult(), which isn't possible. Is there something along the lines of startFragmentForResult() where I can kick off a new fragment and when it's done have it return a result to the previous fragment?

10 Answers

Recently, Google has just added a new ability to FragmentManager which made the FragmentManager be able to act as a central store for fragment results. We can pass the data back and forth between Fragments easily.

You can out the blog post that I've made https://oozou.com/blog/starting-a-fragment-for-results-in-android-46

Starting fragment.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Use the Kotlin extension in the fragment-ktx artifact
    setResultListener("requestKey") { key, bundle ->
        // We use a String here, but any type that can be put in a Bundle is supported
        val result = bundle.getString("bundleKey")
        // Do something with the result...
    }
}

A Fragment that we want the result back.

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact
    setResult("requestKey", bundleOf("bundleKey" to result))
}

The snippet is taken from Google's official documents. https://developer.android.com/training/basics/fragments/pass-data-between#kotlin

At the date of this answer written, this feature is still in alpha state. You can try it out using this dependency.

androidx.fragment:fragment:1.3.0-alpha05

We can simply share the same ViewModel between fragments

SharedViewModel

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel

class SharedViewModel : ViewModel() {

    val stringData: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

}

FirstFragment

import android.arch.lifecycle.Observer
import android.os.Bundle
import android.arch.lifecycle.ViewModelProviders
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

class FirstFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        sharedViewModel.stringData.observe(this, Observer { dateString ->
            // get the changed String
        })

    }

}

SecondFragment

import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGrou

class SecondFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        changeString()
    }

    private fun changeString() {
        sharedViewModel.stringData.value = "Test"
    }

}

There is an Android library - FlowR that allows you to start fragments for results.

Starting a fragment for result.

Flowr.open(RequestFragment.class)
    .displayFragmentForResults(getFragmentId(), REQUEST_CODE);

Handling results in the calling fragment.

@Override
protected void onFragmentResults(int requestCode, int resultCode, Bundle data) {
    super.onFragmentResults(requestCode, resultCode, data);

    if (requestCode == REQUEST_CODE) {
        if (resultCode == Activity.RESULT_OK) {
            demoTextView.setText("Result OK");
        } else {
            demoTextView.setText("Result CANCELED");
        }
    }
}

Setting the result in the Fragment.

Flowr.closeWithResults(getResultsResponse(resultCode, resultData));

The easiest way to pass data back is by setArgument(). For example, you have fragment1 which calls fragment2 which calls fragment3, fragment1 -> framgnet2 -> fargment3

In fragment1

public void navigateToFragment2() {
    if (fragmentManager == null) return;

    Fragment2 fragment = Fragment2.newInstance();
    String tag = "Fragment 2 here";
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .add(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commitAllowingStateLoss();
}

In fragment2 we call fragment3 as usual

private void navigateToFragment3() {
    if (fragmentManager == null) return;
    Fragment3 fragment = new Fragment3();
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .replace(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commit();
}

When we finished our task in fragment3 now we call like this:

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
if (fragmentManager == null) return;
fragmentManager.popBackStack();
Bundle bundle = new Bundle();
bundle.putString("bundle_filter", "data");
fragmentManager.findFragmentByTag("Fragment 2 here").setArguments(bundle);

Now in fragment2 we can easily call arguments

@Override
public void onResume() {
    super.onResume();
    Bundle rgs = getArguments();
    if (args != null) 
        String data = rgs.getString("bundle_filter");
}

A solution using interfaces (and Kotlin). The core idea is to define a callback interface, implement it in your activity, then call it from your fragment.

First, create an interface ActionHandler:

interface ActionHandler {
    fun handleAction(actionCode: String, result: Int)
}

Next, call this from your child (in this case, your fragment):

companion object {
    const val FRAGMENT_A_CLOSED = "com.example.fragment_a_closed"
}

fun closeFragment() {
    try {
        (activity as ActionHandler).handleAction(FRAGMENT_A_CLOSED, 1234)
    } catch (e: ClassCastException) {
        Timber.e("Calling activity can't get callback!")
    }
    dismiss()
}

Finally, implement this in your parent to receive the callback (in this case, your Activity):

class MainActivity: ActionHandler { 
    override fun handleAction(actionCode: String, result: Int) {
        when {
            actionCode == FragmentA.FRAGMENT_A_CLOSED -> {
                doSomething(result)
            }
            actionCode == FragmentB.FRAGMENT_B_CLOSED -> {
                doSomethingElse(result)
            }
            actionCode == FragmentC.FRAGMENT_C_CLOSED -> {
                doAnotherThing(result)
            }
        }
    }

Another thing you could do depending on your architecture is use a shared ViewModel between the fragments. So in my case FragmentA is a form, and FragmentB is a item selection view where the user can search and select an item, storing it in the ViewModel. Then when I come back to FragmentA, the information is already stored !

Related