I want to pass some data from a fragment to an activity.
This question has been asked already many times, and this answer is the best i have found so far.
I have followed the official documentation, but I still haven't got any results. What I have so far in the fragment is:
public class DropPackageFourthFragment1 extends Fragment {
public DropPackageFourthFragment1() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v=inflater.inflate(R.layout.fragment_drop_package_fourth_fragment1, container, false );
passData("hellooooo");
return v;
}
//Pass data to activity
OnDataPass dataPasser;
#Override
public void onAttach(Context context) {
super.onAttach(context);
Activity a;
if (context instanceof Activity){
a=(Activity) context;
dataPasser = (OnDataPass) a;
}
}
public interface OnDataPass {
public void onDataPass(String data);
}
public void passData(String data) {
dataPasser.onDataPass(data);
}
}
And in the main activity i have:
public class DropPackageFourth extends AppCompatActivity implements DropPackageFourthFragment1.OnDataPass{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_drop_package_fourth);
}
#Override
public void onDataPass(String data) {
Log.d("LOG","hello " + data);
}
}
The LogCat doesn't show anything, I feel like i'm missing something, but I cannot find what it is!
Any help would be appreciated!
Use EventBus.
https://github.com/greenrobot/EventBus
or RxAndroid's PublishSubject
https://github.com/ReactiveX/RxAndroid
The code is in fact working, I was just filtering the LogCat and i wasn't seeing anything!
Related
Below is the MainActivity class that I'm using. The code checks to see if the phone is in landscape or portrait. If it's in portrait, it will show the main fragment in the main activity only (the main fragment is a static fragment in the main_activity.xml file). Then if a "Recipe" is clicked it will open a detail activity with its own fragment. If the phone is in landscape mode, it will show the main fragment and the detail fragment side by side. Everything works perfectly fine however when I follow the procedure below I get a white screen instead of the main activity:
Procedure:
Switch to landscape
Switch back to portrait
Choose an item and wait for the detail activity to open
Press back
Here instead of the main activity window I get a white screen
If I don't switch to landscape and just start with the portrait mode everything is fine. It seems like switching to landscape does something that causes the problem and I can't figure out what. Any tip on what's going on or where to look would be much appreciated.
public class MainActivity extends AppCompatActivity implements RecipesFragment.OnRecipeClickListener {
private String RECIPE_PARCEL_KEY;
private boolean mTwoPane;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RECIPE_PARCEL_KEY = getString(R.string.ParcelKey_RecipeParcel);
if (findViewById(R.id.linearLayoutTwoPane) != null) {
mTwoPane = true;
if (savedInstanceState == null) {
RecipeFragment recipeFragment = new RecipeFragment();
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.add(R.id.recipeFrameForTwoPane, recipeFragment)
.commit();
}
} else {
mTwoPane = false;
}
}
#Override
public void OnRecipeClick(Recipe recipe) {
if (mTwoPane) {
RecipeFragment recipeFragment = new RecipeFragment();
recipeFragment.setRecipe(recipe);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.recipeFrameForTwoPane, recipeFragment)
.commit();
} else {
Class destinationClass = DetailActivity.class;
Intent intentToStartDetailActivity = new Intent(this, destinationClass);
intentToStartDetailActivity.putExtra(RECIPE_PARCEL_KEY, recipe);
startActivity(intentToStartDetailActivity);
}
}
}
EDIT:
Adding RecipeFragment's code below:
public class RecipeFragment extends Fragment {
private Recipe mRecipe;
#BindView(R.id.tv_recipeName) TextView recipeNameTextView;
public RecipeFragment(){
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.recipe_fragment,container,false);
ButterKnife.bind(this,view);
if(mRecipe!=null) {
recipeNameTextView.setText(mRecipe.getName());
}else{
recipeNameTextView.setText(getString(R.string.messageSelectARecipe));
}
return view;
}
public void setRecipe(Recipe recipe){
mRecipe = recipe;
}
}
EDIT:
I followed #mt0s's advice and created different background colors for the fragments and activities and finally narrowed down the problem to a line in my recyclerview adapter code. My adapter code is below. Inside loadInBackground() on line URL url = new URL(getString(R.string.URL_RecipeJSON)); I get a Fragment RecipesFragment{96e9b6a} not attached to Activity exception. I don't understand why I'm getting this exception and what the best way to resolve this is. Have I placed the right code in the right fragment methods (ie OnCreate vs OnActivityCreated vs OnCreateView vs etc)?
public class RecipesFragment extends Fragment
implements RecipeAdapter.RecipeAdapterOnClickHandler,
LoaderManager.LoaderCallbacks<ArrayList<Recipe>> {
#BindView(R.id.rv_recipes) RecyclerView mRecyclerView;
private RecipeAdapter mRecipeAdapter;
private static final int LOADER_ID = 1000;
private static final String TAG = "RecipesFragment";
private OnRecipeClickListener mOnRecipeClickListener;
public RecipesFragment(){
}
public interface OnRecipeClickListener {
void OnRecipeClick(Recipe recipe);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.recipes_fragment, container, false);
ButterKnife.bind(this, view);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setHasFixedSize(true);
mRecipeAdapter = new RecipeAdapter(this);
mRecyclerView.setAdapter(mRecipeAdapter);
return view;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLoaderManager().initLoader(LOADER_ID, null, this);
}
#Override
public void onPause() {
super.onPause();
}
#Override
public void onResume() {
super.onResume();
}
#Override
public void OnClick(Recipe recipe) {
mOnRecipeClickListener.OnRecipeClick(recipe);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try{
mOnRecipeClickListener = (OnRecipeClickListener) context;
} catch (ClassCastException e){
Log.e(TAG, "onAttach: Host activity class must implement OnRecipeClickListener.");
}
}
#Override
public Loader<ArrayList<Recipe>> onCreateLoader(int i, Bundle bundle) {
return new AsyncTaskLoader<ArrayList<Recipe>>(getActivity()) {
#Override
protected void onStartLoading() {
super.onStartLoading();
forceLoad();
}
#Override
public ArrayList<Recipe> loadInBackground() {
String response;
ArrayList<Recipe> recipes = null;
try {
URL url = new URL(getString(R.string.URL_RecipeJSON)); //***I get an exception here***
response = NetworkUtils.getResponseFromHttpUrl(url, getActivity());
recipes = RecipeJsonUtils.getRecipeFromJson(getActivity(), response);
} catch (Exception e) {
Log.e(TAG, "loadInBackground: " + e.getMessage());
}
return recipes;
}
};
}
#Override
public void onLoadFinished(Loader<ArrayList<Recipe>> loader, ArrayList<Recipe> recipes) {
mRecipeAdapter.setRecipeData(recipes);
}
#Override
public void onLoaderReset(Loader<ArrayList<Recipe>> loader) {
}
}
I finally figured out the problem and the solution. The problem is that onStartLoading() in the AsyncTaskLoader anonymous class in RecipesFragment class gets called every time the fragment is resumed whether the enclosing Loader is called or not. This causes the problem. I need to have control over when onStartLoading() is being called and I only want it to be called if and only if the enclosing Loader is being initialized or restarted. As such, I destroyed the loader in onPause() of the fragment and restarted it in onResume(). Hence, I added the following code to the RecipesFragment class:
#Override
public void onPause() {
super.onPause();
getLoaderManager().destroyLoader(LOADER_ID);
}
#Override
public void onResume() {
super.onResume();
getLoaderManager().restartLoader(LOADER_ID, null, this);
}
I also removed initLoader() from onCreate(). This way, every time the fragment is resumed (or created) onStartLoading() will be called. I tried this and it solves my problem.
When you switch from the landscape to portrait or the opposite the Android OS destroy your activity and recreate it again. this what probably trigger your problem
So question. Which of the two is the proper way to do a callback?(Unless both of these are incorrect) Or does it depend on the case.
For example. Let's say i wanted to return something from a Fragment to its Activity.
1)I implement an interface in the Fragment and then on its onAttach I set the listener, and then implement the interface in the activity.
public class UrgentCareFragment{
public interface TestListener {
void finishedTest();
}
TestListener mEventListener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_urgent_care_viewpager_fragment, container, false);
mEventListener.finishedTest();
return v;
}
#TargetApi(23)
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mEventListener = (TestListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement urgentCareListener");
}
}
/*
* Deprecated on API 23
* Use onAttachToContext instead
*/
#SuppressWarnings("deprecation")
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (Build.VERSION.SDK_INT < 23) {
try {
mEventListener = (TestListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement urgentCareListener");
}
}
}
}
Activity Class.
public class testActivity extends AppCompatActivity implements TestListener{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_urgent_care);
}
#Override
public void finishedTest(){
//do what you need to do
}
}
2) I create an interface class. In the Fragment I set the listener to the activities context by using getActivity. And then implement the interface in the activity.
Interface Class
public interface TestListener {
void finishedTest();
}
Fragment
public class UrgentCareFragment{
TestListener mEventListener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mEventListener = (TestListener) getActivity();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_urgent_care_viewpager_fragment, container, false);
mEventListener.finishedTest();
return v;
}
}
Activity Class.
public class testActivity extends AppCompatActivity implements TestListener{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_urgent_care);
}
#Override
public void finishedTest(){
//do what you need to do
}
}
Which is the correct way? My main concern is the way the listener is set. Could either of these two cause issues. Or is there one i should throw out. Both seem to work properly, I am just trying to make sure what the correct way is, or if both are fine.
Thanks
I am trying to send data from activity to fragment and vice versa using interfaces but getting an error of cycling inheritance involving MyFragment.
Implementing interface created in MyFragment:
public class MyActivity implements OnSendFromMyFragListener {
OnSendFromMyActivityListener mCallback;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCallback.sendFromMyActivity(2);
}
#Override
public void sendFromMyFrag (int a) {
//do something
}
public interface OnSendFromMyActivityListener {
public void sendFromMyActivity(int b);
}
}
Implementing interface created in MyActivity:
public class MyFragment extends Fragment implements OnSendFromMyActivityListener {
OnSendFromMyFragListener mCallback;
public interface OnSendFromMyFragListener {
void sendFromMyFrag(int a);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mCallback = (OnSendFromMyFragListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString());
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_my, container, false);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mCallback.sendFromMyFrag(1);
}
}
});
return view;
}
#Override
public void sendFromMyActivity(int b) {
//do something
}
}
Well the reason you are getting this error is because your Activity depends on your Fragment and Fragment depends on your Activity. Don't Agree?
Let me show you. Imagine you are a compiler in your work you stumbled across:
class A implements B.A {
interface B {
void foo1();
}
#Override
public void foo2()
{
// do something;
}
}
Now you know that class A depends on (implements) B.A, so before you go further into class A you move on to class B:
class B implements A.B {
interface A {
void foo2();
}
#Override
public void foo1()
{
// Do Something;
}
}
Now you see that class B depends on (implements) class A specifically class A.B! What do you (compiler) do? Go back to class A? But that depends on class B. So you see this becomes an unending cycle thus causing a cyclic dependency where both your class' definitions depend on each other.
As an alternative you could either create a member event listener or an anonymous one. Or if you don't like either of those options, you could also create a separate java interface class file for any one of the two interfaces.
Maybe you have to do this for Activity
public class MyActivity extends AppCompatActivity implements MyFragment.OnSendFromMyFragListener {
public interface OnSendFromMyActivityListener {
void sendFromMyActivity(int b);
}
OnSendFromMyActivityListener mCallback;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyFragment myFragment = MyFragment.newInstance();
//Do the transaction.....
//And after this
mCallback.sendFromMyActivity(0);
}
public void setOnSendFromMyActivityListener(OnSendFromMyActivityListener mCallback){
this.mCallback = mCallback;
}
#Override
public void sendFromMyFrag(int a) {
//do something
}}
And this for Fragment
public class MyFragment extends Fragment implements OnSendFromMyActivityListener {
OnSendFromMyFragListener mCallback;
public interface OnSendFromMyFragListener {
void sendFromMyFrag(int a);
}
public static MyFragment newInstance() {
Bundle args = new Bundle();
MyFragment fragment = new MyFragment();
fragment.setArguments(args);
return fragment;
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mCallback = (OnSendFromMyFragListener) getActivity();
((MyActivity) getActivity()).setOnSendFromMyActivityListener(this);
} catch (ClassCastException e) {
throw new ClassCastException(context.toString());
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_place_suggest, container, false);
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mCallback.sendFromMyFrag(1);
}
});
return view;
}
#Override
public void sendFromMyActivity(int b) {
//do something
}
if you want you can put setters of the interface also to the Fragment for better abstraction.
Something you have to watch out is to look for nulls interfaces
Thanks
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I want to pass data from an activity to a fragment, using an interface.
Please have a look at the code snippets below:
Interface:
public interface FragmentCommunicator {
public void passData(String name);
}
MainActivity:
public class MainActivity extends AppCompatActivity{
FragmentCommunicator fragmentCommunicator;
private Fragment fragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button b = (Button) findViewById(R.id.button);
b.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
fragment= new Fragment();
fragmentCommunicator = (FragmentCommunicator) getApplication();
fragmentCommunicator.passData("hello");
getSupportFragmentManager().beginTransaction().replace(R.id.container ,fragment).commit();
}
});
}
}
Fragment:
public class Fragment extends android.support.v4.app.Fragment implements FragmentCommunicator {
FragmentCommunicator communicator;
Context c;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment, null);
return view;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
this.c = context;
}
#Override
public void passData(String name) {
Toast.makeText(c, name, Toast.LENGTH_SHORT).show();
}
}
I just want to pass some string when I click on button, (or some other event) to launch a fragment, and when the fragment is launched, it should show a toast containing that string...
Please help
any help would be appreciated.
Write this line of code after onCreate method.
public void passVal(FragmentCommunicator fragmentCommunicator) {
this.fragmentCommunicator = fragmentCommunicator;
}
Something like this
public class MainActivity extends AppCompatActivity{
FragmentCommunicator fragmentCommunicator;
private Fragment fragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button b = (Button) findViewById(R.id.button);
fragment= new Fragment();
//App is crasing for this line. Working fine by removing it
//fragmentCommunicator = (FragmentCommunicator) getApplication();
//fragmentCommunicator.passData("hello");
getSupportFragmentManager().beginTransaction().replace(R.id.container ,fragment).commit();
b.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
fragmentCommunicator.passData("Hello");
}
});
}
//Here is new method
public void passVal(FragmentCommunicator fragmentCommunicator) {
this.fragmentCommunicator = fragmentCommunicator;
}
}
Then write this line of code into onCreateView() of your fragment. Something like this
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment, null);
((MainActivity) getActivity()).passVal(new FragmentCommunicator() {
#Override
public void passData(String name) {
Toast.makeText(c, name, Toast.LENGTH_SHORT).show();
}
});
return view;
}
Note: no need to implement FragmentCommunicator interface in your fragment. Hope it works. It works for me. I have tested
Activities contain fragments, you shouldn't need to pass anything. One technique would be to store your data in the activity scope and get a reference to it with getActivity().
Best practice to reference the parent activity of a fragment?
As far as the code you posted, I don't see where you call your pass data method within the fragment. I would suggest calling it in onviewcreated().
I will attempt to provide some sample code later as doing this on my mobile device is proving difficult.
Try passdata (getActivity ().someStringVariable);
In your Fragment class, have a TAG field. Naming a fragment Fragment can be a bit confusing as it is the same name as an Android Fragment, so I will use ExampleFragment:
public class ExampleFragment extends android.support.v4.app.Fragment implements FragmentCommunicator {
public static final String TAG = "ExampleFragment";
// ...
}
Now, when you replace the fragment in the activity, make sure to pass the TAG:
getSupportFragmentManager().beginTransaction().replace(R.id.container ,fragment, ExampleFragment.TAG).commit();
Note that in your case it would be Fragment.TAG, but I used ExampleFragment to clarify that Fragment.TAG is not part of the Android SDK.
To get a reference of your fragment in your activity, use the FragmentManager to find your fragment:
Fragment myFragment = getSupportFragmentManager().findFragmentByTag(ExampleFragment.TAG);
You can then make sure that myFragment is not null and cast it to FragmentCommunicator:
if (myFragment != null) {
fragmentCommunicator = (FragmentCommunicator) myFragment;
}
Note that in your example fragmentCommunicator = (FragmentCommunicator) getApplication(); is invalid because it is your fragment that is implementing the interface, not your application.
I have an Activity that starts a Fragment. Inside that fragment i have an EditText. And when i get the text that the user types there, i want to get that from my Activity with the help of an interface. I'm using this guide
On my MainActivity i'm implementing the commentListener interface and i've managed to get the result inside the onCommentEntered method. But on doneListener, which is triggered when the user presses a button finishing the activity, i'm getting null.
Obviously onCommentEntered runs after doneListener.
Any suggestions on how to get the result on doneListener?
class MainActivity implements fragment.commentListener{
static String comment;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.addtransaction);
done=(TextView)findViewById(R.id.done_button);
}
// called when user presses a button and MainActivity finishes.
public void doneListener(View v){
// Here i get NULL
System.out.println(comment);
finish();
}
#Override
public void onCommentEntered(String data) {
comment=data;
// Here i get what the user typed
System.out.println(comment);
}
}
My fragment
public class thefragment extends Fragment {
commentListener cListener;
static TextView note;
EditText comment;
public interface commentListener{
void onCommentEntered(String data);
}
public static thefragment newInstance(){
return new thefragment ();
}
public thefragment (){
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
cListener=(commentListener) context;
}
#Override
public void onDetach() {
super.onDetach();
cListener=null;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v;
v=inflater.inflate(R.layout.fragment, container, false);
comment=(EditText)v.findViewById(R.id.comment_picker);
comment.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
cListener.onCommentEntered(comment.getText().toString());
}
}
});
return v;
}
}
Create a new Interface, as well as implement the same in Fragment. create a method in the interface and override the same in Fragments. While calling the fragment from Activity create an Interface type fragment and call the interface method.
eg:
public class thefragment extends Fragment implement fragmentNofyInterface {
...
#Override
protected void notify(String txt){
mTvTxt.setText(txt);
}
.....
}
Interface Format
public interface fragmentNofyInterface {
protected void notify(String txt);
}
Activity format
class MainActivity implements fragment.commentListener{
.....
private fragmentNofyInterface mFragmentNotifier;
.........
thefragment mFragment = new thefragment();
mFragmentNotifier = (fragmentNofyInterface ) mFragment;
FragmentTransaction transaction = mFragmentMngr.beginTransaction().
add(R.id.rl_fragment_navigation_container,
mFragment);
transaction .commit();
......
//Notify the fragment when you required
mFragmentNotifier.notify("hello world");
}
Switching to addTextChangedListener and TextWatcher for my EditText, like this here,
seems to fix my problem.Now onCommentEntered method is called before doneListener and thus String commentgets whatever was typed in the EditText.Thanks everyone for helping.