Fragment communicating with an Activity - java

I am confused about how communication with a Fragment and an Activity is made. For example, an interface was defined here (https://developer.android.com/training/basics/fragments/communicating.html).
public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback;
// Container Activity must implement this interface
public interface OnHeadlineSelectedListener {
public void onArticleSelected(int position);
}
...
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
// The user clicked on a list item.
mCallback.onArticleSelected(position);
}
}
Eventually, the following method is called from the MainActivity.
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
...
public void onArticleSelected(int position) {
// Do something
}
}
My questions are:
How does mCallback "know" which onArticleSelected method to call (as there might be other classes that have implemented OnHeadlineSelectedListener).
mCallback.onArticleSelected(position);
I wouldn't be confused if it went:
mCallback = new OnHeadSelectedListener() {
#Override
public void onArticleSelected(int position)
// Do something
}
and then mCallback is referred in some way in MainActivity to utilize the onArticleSelected method in some way. In the example code, however, the line intelligently sticks to "an" interface. How does that happen?
Also, I found that the Log I implemented onArticleSelected method from MainActivity is called previously to the one in onListItemClick method in HeadlineFragment. Is it expected?

Since you defined the OnHeadlineSelectedListener interface with only one method, and your activity implements it, there's no ambiguity in choosing the method when you use the activity as instance of this interface, 'cause all you know about activity while using it as instance of OnHeadlineSelectedListener interface is a presence of onArticleSelected(int) method in it.
It depends on when you call the logging function - before or after calling the callback method.
P.S. While this kind of communication between Activity and Fragment (or any other objects) is perfectly fine, personally I prefer the Event Bus approach, 'cause it gives us a possibility to organize code in a low coupled manner. Here are some nice implementations of Event Bus pattern:
https://github.com/greenrobot/EventBus
http://square.github.io/otto/
Take a look at them if you are interested in this approach.

Your mCallback is your activity, in the onAttach method of your fragment, you will set the activity as listener for your fragment. By this way, this is normal that the activity is notified when you call mCallback.onArticleSelected(position);

For your first part of question
You should have a look on onAttach and onDetach methods-
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallbacks = (OnHeadlineSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException("Activity must implement OnHeadlineSelectedListener.");
}
}
#Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
Second part-
The behavior is not expected.
You should not make your activity class static as well.

Related

FragmentManager has not been attached to a host [duplicate]

I have an Actvivity with a ListView, I set an adapter
MyAdapter extends BaseAdapter
that adapter has a callback interface
OnPdfClickedListener callback;
public interface OnPdfClickedListener {
public void onPdfClicked();
}
and in the Activity
MyActvivity implements MyAdapter.OnPdfClickedListener
and
#Override
public void onPdfClicked() {
Log.d("TEST", "PDF CLICKED in ACTVIIVTY");
}
This is pretty much the same as described here, which works like charm for fragments.
When trying the same from the adapter the callback object is null.
I also tried instantiating like
OnPdfClickedListener callback = new OnPdfClickedListener() {
#Override
public void onPdfClicked() {
// TODO Auto-generated method stub
}
};
I have no errors then but the respective method in the Activity is never called.
My question is 1. why isn't the callback object null when used in a fragment, it's never instantiated
and 2. how can I callback to an Activity from an adapter?
why isn't the callback object null when used in a fragment, it's never instantiated
Your Activity most likely isn't registered as a listener, with the callback variable you created(which has nothing to do with the Activity) an instance of OnPdfClickedListener and (probably) used that when the event happened.
how can I callback to an Activity from an adapter?
Pass a reference to the Activity to your MyAdapter class, cast it to OnPdfClickedListener and use that to call onPdfClicked instead of the current callback variable.
Try adding this:
In your Activity which implements the interface:
new MyAdapter(this,....);
In your Adapter:
MyAdapter(Context context, ...){
callback = (OnPdfClickedListener)context;
}

ViewHolder from Adapter from Fragment starts Activity, how can the Activity talk back to the Fragment?

PlaylistFragment starts an adapter:
playlistsAdapter = new PlaylistRecyclerAdapter(playlistsListArray, addToPlaylist, mSong, getActivity(), this);
PlaylistRecyclerAdapter binds data to the PlaylistViewHolder, something like this:
((PlaylistViewHolder) viewHolder).bind(this, dataSet.get(position), addToPlaylist, mSong);
User clicks on an item in PlaylistViewHolder:
context.startActivity(PublicPlaylistActivity.createStartIntent(context, playlist));
Now here is the question, how can PublicPlaylistActivity talk back to the initial PlaylistFragment?
I suggest you'd better use Interface from fragment to adapter. So when user clicks anything in adapter, call function realization in fragment. If you need your activity to proceed some operation - ((YourActivity) getActivity()).someMethod() should be called from fragment.
Second trick is using broadcastreceiver to send events. A bit more complicated. You have to launch broadcast in view you need to recive message and send these messages from adapter. This approach is more complexible to debug and support if system is wide spread, so you'd better use interfaces.
There are several ways of doing that. The simplest way should be starting the PublicPlaylistActivity with startActivityForResult. In that way, then the activity finishes, you can set send some data to the caller fragment (which is PlaylistFragment in your case). Here is a nice tutorial about the implementation.
Another way of doing that is by using lifecycle methods. You might have a public static variable which can keep track of some status that you might observe in your onResume function of your PlaylistFragment when you are returning back from your PublicPlaylistActivity. You might consider a sample implementation as follows.
Define a public static variable in your PlaylistFragment. Then in your onResume function check the value of that variable and take actions accordingly.
public static boolean someIndicator = false; // Initialize with a default value
#Override
protected void onResume() {
super.onResume();
if(someIndicator == true) doSomething();
else doSomethingElse();
}
Now you can set the indicator variable from anywhere in your application actually which will have the effect on your PlaylistFragment. For example, from your PublicPlaylistActivity, you might consider doing something like this.
public void someFunctionInYourPublicPlaylistActivity() {
// ...
// Some code and then the following
PlaylistFragment.someIndicator = true;
}
Another way of achieving the same thing is by using a BroadcastReceiver. Here is a tutorial on how you can implement one.
It really depends on how you are structuring your whole activity-fragments communication. Hope that helps!
I would do a common "context" class (ComContext) with an interface. When you create your fragment, you also create this class. And from the activity you can check if it exists or not.
I assume that you already have a helper(AppHelper) class with static variables.
public class AppHelper {
public static ComContext comContext = null;
}
public class MainFragment {
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ConContext comContext = new ComContext();
comContext.listener = this;
AppHelper.comContext = comContext;
}
#Override
public void onDataChanged() {
}
#Override
public void onDestroyView() {
super.onDestroyView();
AppHelper.comContext = null;
}
}
public class MainActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (AppHelper.comContext != null) {
AppHelper.comContext.listener.onDataChanged();
}
}
}
public class ComContext {
public interface HelperListener {
void onDataChanged();
}
public HelperListener listener = null;
}

Use Asynctask for multiple Fragments

I have 2x fragment java classes, which needs to use the same asynctask. Are there anyway i can do this, or do i have to copy/paste the same asynctask in both fragment classes? i have these 3 java classes in total:
MainActivity
dataTabelFragment
sensorOverviewFragment
The asynctask is fetching data from json URL.
Maybe it's possible to make another java class for the asynctask?
All help is appreciated!
What you say suggest you should decouple your async task code from both fragments and instead have separate worker (perhaps IntenstService would server you well here instead) that you fragments would call to have the job done. Or, depending on your code structure (you should think of this if you haven't yet :) maybe your async task code should be part of you fragments' base class that both of them would then extend.
I guess your fragments are in same Activity (MainActivity), so you can put your AsyncTask in your MainActivity.class and your fragments can communicate with your Activity with listener. Something like this:
public class YourFragment extends Fragment{
private YourListener mListener;
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mListener = (YourListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement YourFragment");
}
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
interface OnSearchFilterListener {
void onItemClick(int position);
}
}

How do I communicate between a class and a fragment which uses it?

I'm using Android Studio. I haven't been able to find an answer online, so even a link to a solution would be helpful.
I have an Activity, which includes a number of Fragments. One of these Fragments is called BookGridFragment, which uses a class called BookGrid.
BookGridFragment looks like this (I've left out irrelevant bits):
public class BookGridFragment extends Fragment {
BookGrid myBookGrid;
public BookGridFragment() {}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
// Inflate layout
View rootView = inflater.inflate(
R.layout.fragment_book_grid, container, false);
myBookGrid = rootView.findViewById(book_grid);
return rootView;
}
public void setBook(Book thisBook) {
myBookGrid.setBook(thisBook);
}
}
The BookGrid class is:
public class BookGrid extends View {
private Book mBook;
public BookGrid(Context thisContext, AttributeSet attrs) {
super(thisContext, attrs);
}
public void setBook(Book newBook) {
mBook = newBook;
}
protected void onDraw(Canvas canvas) {
if (mBook == null) return;
canvas.save();
draw_book_details();
// draw_book_details() is a function which just takes
// the book info and displays it in a grid
canvas.restore();
}
public boolean onTouchEvent(MotionEvent event) {
// This function responds to the user tapping a piece of
// book info within the grid
// THIS IS WHERE I'M HAVING PROBLEMS
}
}
So, that all works fine. The issue is, that I need the BookGridFragment to know when the user touches the BookGrid and to pass that information to another Fragment (via the Activity). So, I assume that when the onTouchEvent is reached, that should somehow notify the BookGridFragment that the BookGrid was touched, but I can't figure out how to do that.
Everything I've found online is about passing information between Fragments, but that approach doesn't work here as the BookGrid class doesn't "know" that it's within a BookGridFragment.
You could use the same idea that is used to communicate a Fragment and an Activity. Create an interface:
public interface OnBookGridTouched{
void onTouchGrid();
}
Add a variable to your BookGrid:
private OnBookGridTouched mCallback;
Add a setter to this variable:
public void setCallback(OnBookGridTouched callback){
mCallback = callback;
}
Then make your fragment implement the interface:
public class BookGridFragment extends Fragment implements OnBookGridTouched {
You'll be forced to implement the method onTouchGrid
In your fragment onCreateView pass the fragment to your custom view:
myBookGrid.setCallback(this);
Finally, in your custom view you can call the callback to reference the fragment:
public boolean onTouchEvent(MotionEvent event) {
// This function responds to the user tapping a piece of
// book info within the grid
// THIS IS WHERE I'M HAVING PROBLEMS
mCallback.onTouchGrid();
}
A solution could be to set the onTouch/onClick listener in the fragment instead of in the BookGrid itself. From there you can use the fragment method getActivity() to call an activity method, parsing on the correct data to the correct fragment.
I think this situation is very similar to a Fragment containing a Button.
The Button has a method which accepts something implementing a certain interface (for the Button: View.OnClickListener). The Fragment calls that method (for the Button: setOnClickListener()) to pass in the desired Object implementing all the required methods, either an anonymous class or maybe a field or the Fragment itself. There are pros and cons for all three approaches, it depends on your situation which one is best.
They have in common that BookGrid should have an interface as well as a method so other classes can set the current Object implementing that interface.
I am not quite sure about the exact scenario that you are having there. However, if the problem is the communication between the fragment and an activity which hosts the fragment, then you might think of the following implementation.
Let me point out some of your concerns first.
Everything I've found online is about passing information between
Fragments, but that approach doesn't work here as the BookGrid class
doesn't "know" that it's within a BookGridFragment.
BookGrid class will know the context of its existence when you will pass the Context towards it while calling a function of it. So I would like to suggest passing the context of the Activity or Fragment when you are calling a function from your BookGrid class.
public class BookGrid extends View {
private Book mBook;
private Context context;
public BookGrid(Context thisContext, AttributeSet attrs) {
super(thisContext, attrs);
this.context = thisContext;
}
public void setBook(Book newBook) {
mBook = newBook;
}
protected void onDraw(Canvas canvas) {
if (mBook == null) return;
canvas.save();
draw_book_details();
// draw_book_details() is a function which just takes
// the book info and displays it in a grid
canvas.restore();
}
public boolean onTouchEvent(MotionEvent event) {
// Call the function of your host activity
((YourActivity)(thisContext)).onBookGridTouched();
}
}
Now write a public method in your activity class which hosts the fragment named onBookGridTouched.
public void onBookGridTouched() {
// Communicate with other fragments here
}
However, a noble approach of solving this problem in a more generic way is to use an interface and then implement the interface wherever necessary like #LeviAlbuquerque suggested.
I am just putting another workaround which is a bit static.
Assuming that you have ONE Activity responsible of all fragments:
1.Create an interface in your BookGrid:
public interface ActionHappened {
void onActionHappened();
}
2.Create an instance of your interface within your BookGrid class and trigger the method onActionHappened where you want it to be triggered. For instance, if you would like it to happen in your onDraw(), then do the following:
ActionHappened actionHappened;
protected void onDraw(Canvas canvas) {
if (mBook == null) return;
canvas.save();
draw_book_details();
// draw_book_details() is a function which just takes
// the book info and displays it in a grid
canvas.restore();
actionHappened.onActionHappened();
}
3.Implement your interface within your activity
public class ActivityA extends AppCompatActivity implements BookGrid.ActionHappened {}
4.Within your implemented method, trigger the method:
#Override
public void onActionHappened() {
Fragment fragmentA = getSupportFragmentManager().findFragmentByTag(R.id.fragmentA);
Fragment fragmentB = getSupportFragmentManager().findFragmentByTag(R.id.fragmentB);
//Trigger that method from your activity to fragmentA or fragmentB
fragmentA.doWork();
fragmentB.doWork();
}
Wether you would like to pass data to fragmentA or fragmentB, doWork() method will do that for you. Make you create such a method in the corresponding fragment.

Communication between Fragments without Using Interface

I was trying to communicate between two fragments, but I'm just a beginner so I want to know if there is any solution not using interface....
If there are Fragment A, B, and their Activity:
Sending values from Fragment A to Activity
(In here, Fragment A is a current state)
Sending values from Activity to Fragment B
I know direct communication between two fragments is almost impossible,
but I don't think that makes me to use an interface.
Also, is there any method to use like putExtra() for fragment? I only know using Serializable.
Have a look at the Android deverlopers page: http://developer.android.com/training/basics/fragments/communicating.html#DefineInterface
Basically, you define an interface in your Fragment A, and let your Activity implement that Interface. Now you can call the interface method in your Fragment, and your Activity will receive the event. Now in your activity, you can call your second Fragment to update the textview with the received value
// You Activity implements your interface
public class YourActivity implements FragmentA.TextClicked{
#Override
public void sendText(String text){
// Get Fragment B
FraB frag = (FragB)
getSupportFragmentManager().findFragmentById(R.id.fragment_b);
frag.updateText(text);
}
}
// Fragment A defines an Interface, and calls the method when needed
public class FragA extends Fragment{
TextClicked mCallback;
public interface TextClicked{
public void sendText(String text);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (TextClicked) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement TextClicked");
}
}
public void someMethod(){
mCallback.sendText("YOUR TEXT");
}
#Override
public void onDetach() {
mCallback = null; // => avoid leaking, thanks #Deepscorn
super.onDetach();
}
}
// Fragment B has a public method to do something with the text
public class FragB extends Fragment{
public void updateText(String text){
// Here you have it
}
}
You can communicate between fragments directly by using EventBus - send ordinary or sticky events by one fragment and subscribe to that event in another one.
If don't want message to be lost, use sticky events - it work as sticky Intent in Android. It will be around until it is removed by targer fragment or because another event is pending.
Yes you can transfer data between fragments using bundle like you do in Activity using putExtra
Bundle = bundle = new Bundle();
bundle.putString("key","value");
bundle.putSerializable("serialzedKey",SerializedValue);
FragmentTransaction fts = ((BaseActivity) mContext).getSupportFragmentManager().beginTransaction();
fragment.setArguments(bundle);
fts.add(R.id.fragmentHolder, fragment);
fts.addToBackStack(fragment.getClass().getSimpleName());
fts.commit();
In other fragment you can retrieve data using getArguments()
String key = getArguments().getString("key");
SerializedModel = getArguments().getSerializable("serialzedKey");
you can call a method from the parent activity class that calls a method from fragment B like ((YourActivity)getActivity()).callMethod(T yourData)
Take a look at my Github repo on using interfaces to communicate between fragments.
This is just a really simple example but displays the key concepts.
https://github.com/stoddayy/FragmentInteractionExample

Categories

Resources