有没有类似于结果的开始片段的方法?

我现在有一个碎片覆盖。这是用于登录服务的。在手机应用程序中,我想在叠加中显示的每个步骤都是它们自己的屏幕和活动。登录过程有3个部分,每个部分都有自己的用 startActivityForResult ()调用的活动。

现在我想使用片段和叠加来做同样的事情。覆盖将显示与每个活动对应的片段。问题在于这些片段驻留在 Honeycomb API 中的一个活动中。我可以让第一个片段工作,但是接下来需要 startActivityForResult () ,这是不可能的。有没有类似 startFragmentForResult ()的方法,可以启动一个新的片段,并在完成后将结果返回给前一个片段?

76293 次浏览

All of the Fragments live inside Activities. Starting a Fragment for a result doesn't make much sense, because the Activity that houses it always has access to it, and vice versa. If the Fragment needs to pass on a result, it can access its Activity and set its result and finish it. In the case of swapping Fragments in a single Activity, well the Activity is still accessible by both Fragments, and all your message passing can simply go through the Activity.

Just remember that you always have communication between a Fragment and its Activity. Starting for and finishing with a result is the mechanism for communication between Activities - The Activities can then delegate any necessary information to their Fragments.

If you wish, there are some methods for communication between Fragments,

setTargetFragment(Fragment fragment, int requestCode)
getTargetFragment()
getTargetRequestCode()

You can callback using these.

Fragment invoker = getTargetFragment();
if(invoker != null) {
invoker.callPublicMethod();
}

My 2 cents.

I switch beween fragments by swapping an old fragment with a new one using hide and show/add (existing/new). So this answer is for devs who use fragments like I do.

Then I use the onHiddenChanged method to know that the old fragment got switched to back from the new one. See code below.

Before leaving the new fragment, I set a result in a global parameter to be queried by the old fragment. This is a very naive solution.

@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) return;
Result result = Result.getAndReset();
if (result == Result.Refresh) {
refresh();
}
}


public enum Result {
Refresh;


private static Result RESULT;


public static void set(Result result) {
if (RESULT == Refresh) {
// Refresh already requested - no point in setting anything else;
return;
}
RESULT = result;
}


public static Result getAndReset() {
Result result = RESULT;
RESULT = null;
return result;
}
}

In your fragment you can call getActivity(). This will give you access to the activity that created the fragment. From there you can call your customize method to set the values or to pass the values.

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 !

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"
}


}

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