I recently started coding my first Android project using Android Studio 3.1.2 and SDK 19.
One of my fragments contains a RecyclerView with a custom RecyclerView.Adapter attached. On the CardView the Adapter gets by its ViewHolder, there can be a button. The target is, if the button is pressed, a method of my fragment should be called, though it's an instance of a custom subclass of Fragment:
From RequestingFragment:
public abstract class RequestingFragment extends Fragment implements RequestCallbacks {
public final static void startRequest(final RequestOperation, String param) {
//this is the guy i want to call
}
//these are the RequestCallbacks, they're all getting called in startRequest()
public void onSuccess(JSONObject json, String parsingkey) { }
public void onError() { }
public void onFinished() { }
Now one of my RequestingFragments contains a RecyclerView, on which a custom ErrorCompactAdapter is attached. Inside the Adapters ViewHolder, where I load the layout for the single CardViews, there's a button, which should call startRequest() onClick from my RequestingFragment
From ErrorCompactAdapter:
public class ErrorCompactAdapter extends RecyclerView.Adapter<ErrorCompactAdapter.ErrorCompactViewHolder> {
private Context context;
private ArrayList<Error> errors;
public ErrorCompactAdapter(Context context, ArrayList<Error> errors) {
this.context = context;
this.errors = errors;
}
public void onBindViewHolder(ErrorCompactViewHolder, int position) {
//...
holder.errorTakeOverButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//here's were i'm stuck
}
});
//...
}
}
My first approach was to change the context attribute of ErrorCompactAdapter to a RequestingFragment, so that I can call startRequest() on this.
private Context context; // private RequestingFragment attacher;
public void onClick(View v) {
attacher.startRequest(/*params*/);
}
But i'm very unsure, if the fragment that contains the RecyclerView will be the one which receives the response of the request, or if a somehow "pseudo-anonymous" Fragment will receive the response and simply does nothing with it then. Can someone enlight me, if this is the correct path? Thanks in advance.
Pass the Fragment in you ErrorCompactAdapter class's constructor. This works for me the way I want. I had the same issue.
RequestingFragment mFragment;
public ErrorCompactAdapter(Context context, ArrayList<Error> errors,
RequestingFragment fragment)
{
this.context = context;
this.errors = errors;
this.mFragment = fragment;
}
// While passing the fragment into your adapter, do it this way.
ErrorCompactAdapter errorCompactAdapter = new ErrorCompactAdapter(
context, errors, RequestingFragment.this);
holder.errorTakeOverButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// use you method of fragment here
mFragment.startRequest();
}
});
Related
I have a fragment (FragmentSearchResults) that contains results retrieved from a database, in which there is a button "filters". When the user taps on such a button, a class (FiltersDialog) extending a BottomSheetDialogFragment is instantiated, so that the user can set his filters. When the user closes the FiltersDialog activity, the values are passed from FiltersDialog to FragmentSearchResults.
public class FragmentSearchResults extends Fragment implements FiltersDialog.FilterListener {
/* code */
ImageButton btnFilter = myFragment.findViewById(R.id.btn_filters);
btnFilter.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
showFilters();
}
});
}
private void showFilters() {
FiltersDialog filtersDialog = new FiltersDialog();
filtersDialog.show(((FragmentActivity) mContext).getSupportFragmentManager(), "argument");
}
#Override
public void onAttach(#NotNull Context context) {
super.onAttach(context);
mContext = context;
}
#Override
public void onFiltersSet(Map filters) {
// apply filters selected by user
}
public interface FilterListener {
void onFiltersSet(Map filters);
}
}
public class FiltersDialog extends BottomSheetDialogFragment {
private FilterListener mListener;
private Map<String, Object> mFilters;
public FiltersDialog() {
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.layout_filters_dialog, container, false);
TextView txtSave = v.findViewById(R.id.txt_save_filters);
mTxtSave.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mListener.onFiltersSet(mFilters);
}
});
return v;
}
public interface FilterListener {
void onFiltersSet(Map filters);
}
#Override
public void onAttach(#NotNull Context context) {
super.onAttach(context);
if (context instanceof FilterListener) {
mListener = (FilterListener) context;
}
else {
// Here's the error, as the activity Home.java containing FragmentSearchResults
// does not implement FilterListener, FragmentSearchResults does
throw new RuntimeException(context.toString() + " must implement FilterListener");
}
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
}
The problem is that FilterListener needs to be implemented in FragmentSearchResults, but I am passing the activity Home context.
How can I implement FilterListener in the fragment?
Why don't you create method inside your FiltersDialog, like
public void setFiltersListener(FiltersDialog.FilterListener listener) {
mListener = listener;
}
and simply call it after you instantiate the dialog.
FiltersDialog filtersDialog = new FiltersDialog();
filtersDialog.setFiltersListener(this);
filtersDialog.show(((FragmentActivity) mContext).getSupportFragmentManager(), "argument");
Then you can use the listener inside dialog. something like this
if (mListener != null) {
mListener.onFiltersSet(mFilters);
}
How can I setup listener to the dialog?
parameter of onAttach in Fragment is FragmentHost(Activity). thus, it can't typecast to FilterListener.
I suggest a simple way to implement FilterListener setter in FragmentDialog as below code.
... in FiltersDialog
public void setListener(FilterListener listener) {
mListener = listener;
}
...
... in FragmentSearchResults
private void showFilters() {
FiltersDialog filtersDialog = new FiltersDialog();
filtersDialog.setListener(this);
filtersDialog.show(((FragmentActivity) mContext).getSupportFragmentManager(), "argument");
}
...
//When FragmentSearchResults recreated, FiltersDialog must also need to be recreated.
A better approach will be to use LiveData, ViewModel in this case. Use Shared ViewModel Approach, An Activity Level ViewModel can be accessed via all the fragments lying in its environment.
Make an Activity Level ViewModel
Define a LiveData in ViewModel
When your "FragmentSearchResults" opens for the first time, start
observing it.
When You open "FiltersDialog" screen and click save button, Then post
to LiveData changes in the filter (You have activity context here,
You can fetch ActivityViewModel here, get LiveData from it, post
changes to this LiveData)
Now As "FragmentSearchResults" is already observing changes in the
LiveData, You will get callback here, make changes accordingly. This way your code will be completely decoupled. You will be escaped from
hustles of Interfaces.
I simplified my code for you to get better understanding it.
I have MyAdapter and MyActivity working both perfectly.
class MyAdapter extends RecyclerView... {
...
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
...
holder.mImageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((Listener) context).onSelected(object);
}
});
}
...
}
class MyActivity implements MyAdapter.Listener {
...
#Override
public void onSelected(Object object) {
// do things with object here
}
...
}
I want to make my another activity MyAnotherActivity implement MyAdapter.Listener.
When I run my app, and click on my object, an overriden onSelected() just ignored.
public class MyAnotherActivity implements MyAdapter.Listener {
...
#Override
public void onSelected(Object object) {
Log.e("MyAnotherActivity", "This text doesn't shows");
}
...
}
The used Listener implementation depends on the context parameter passed to MyAdapter constructor. Because on this line ((Listener) context).onSelected(object); you are casting the context field of the MyAdapter class to Listener implementation.
So when you are calling the constructor of the MyAdapter, you need to pass the context of MyAnotherActivity.
public class MyAnotherActivity implements MyAdapter.Listener {
#Override
protected void onCreate(Bundle savedInstanceState) {
...
MyAdapter adapter = new MyAdapter(this /*Context of MyAnotherActivity*/, ...);
...
}
}
If you want to implement any methods inside of any class (not just activity) , you can use EventBus, a lightweight library for passing messages and events around.
It's very easy to implement and the code samples will help you out along the way
http://greenrobot.org/eventbus/
I have 2 Recyclerviews inside 2 different Fragments contained in a ViewPager. When a card in my RecyclerviewA is clicked I would like to pass the respective object over to RecyclerviewB currently I have an onClick in RecyclerView with the following code:
#Override
public void onBindViewHolder(ViewHold holder, int position) {
holder.nameTextView.setText(list.get(position).getString("name"));
holder.addressTextView.setText(list.get(position).getString("fromAddress"));
final int pos = position;
holder.acceptButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ApplicationClass.bus.post(list.get(pos));
}
});
In RecyclerViewB I have
public PickupListAdapter(Context context, List<ParseObject> ddList) {
this.ddList = ddList;
this.mContext = context;
ApplicationClass.bus.register(mContext);
}
#Subscribe
public void answerAvailable(ParseObject object) {
ddList.add(object);
notifyDataSetChanged();
}
Inside my Application Class I have:
public static Bus bus;
#Override
public void onCreate(){
super.onCreate();
bus = new Bus(ThreadEnforcer.MAIN);
}
The "answerAvailable" method is not called. What is currently incorrect regarding my implementation?
I can see only one change from my working code. Can you try changing this:
bus= new Bus(ThreadEnforcer.ANY);
I need to transmit data from my activity layer to a view (or at least its fragment) that is not a child of AdapterView.
For a ListView, I could do this very easily with its adapter, but I am stuck on how to reproduce this behavior for a non AdapterView widget (for clarity, let's say a TextView).
I don't want to keep a reference to my fragment (or worse, the view) at Activity level.
Any ideas ?
One way to do this is to use java.util.Observable/Observer :
import java.util.Observable;
import java.util.Observer;
public class MyTextView extends View implements Observer{
#Override
public void update(Observable observable, Object data) {
this.setText((String)data);
}
}
Then, you need an Observable class :
import java.util.Observable;
public class MyObservable extends Observable {
public void setText(String text){
notifyObservers(text);
}
}
Activity :
public class MainActivity extends Activity {
TextView tv;
#Override
public void onCreate(Bundle savedInstanceState) {
...
MyObservable mtv = new MyTextView(getApplicationContext());
MyTextViewModel mm = new MyTextViewModel(10);
mm.addObserver(mtv);
mm.setText("test");
// demonstrated in an activity to shorten the sample, but ideally you would
// keep the observer at activity level and manage the view in the fragment
}
}
------------------------------------------------
Another way to do this is through android.database.DataSetObservable to implement a more traditional Adapter like object :
public class CustomAdapter extends DataSetObservable {
String mText;
public String getText() {
return mText;
}
public void setText(String text) {
mText = text;
}
}
You manipulate it like any other adapter at Activity level :
public class MyActivity extends Activity {
private CustomAdapter mCustomAdapter;
#Override
protected void onCreate() {
...
mCustomAdapter = new CustomAdapter();
}
private void initializeFragment (Fragment fragment) {
// this or whatever method you use to setup your fragments
((MyFragment) fragment).setCustomAdapter(mCustomAdapter);
}
private void onDataLoaded (Stg data) {
// callback method invoked when the network thread has finished loading data
mCustomAdapter.setText(data.text);
mCustomAdapter.notifyChanged();
}
Finally, the only thing missing is the link between your fragment and the view :
public class MyFragment extends Fragment {
private CustomAdapter mCustomAdapter;
public setCustomAdapter(CustomAdapter adapter) {
// this method allows to setup the adapter at startup
mCustomAdapter = adapter;
}
protected DataSetObserver mMyViewObserver = new MyObserver();
private class MyObserver extends DataSetObserver {
#Override
public void onChanged() {
mUpdateHandler.sendEmptyMessage(0);
}
}
private Handler mUpdateHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
updateMyView();
}
};
private void updateMyView() {
if (mView == null) {
return;
}
mView.setMainTextViewText(mCustomAdapter.getText());
}
}
And here you have it. Each time you call notifyChanged(), your observer gets called. In return, he invokes the handler that update the view.
Here you have it, leak free, thread safe custom adapter for any kind of view.
I am trying to create a communication between a custom View and a DialogFragment with an interface/callback.
Custom View:
public MyDraw extends View implements ColorPickerListener
{
public MyDraw(Context context)
{
super(context);
// ...
MyDialogFragment.setColorPickerListener(this);
}
#Override
public void onColorChanged(int color)
{
// ...
}
}
DialogFragment
public MyDialogFragment extends DialogFragment
{
public interface ColorPickerListener
{
public void onColorChanged(int color);
}
ColorPickerListener colorPickerListener;
public static void setColorPickerListener(ColorPickerListener listener)
{
colorPickerListener = listener;
}
// ....
private void colorSelected(int color)
{
colorPickerListener.onColorChanged(color);
}
}
This is working, but I'm not sure if this is Ok. I am afraid of memory leaks, because I am referencing a static method from View to the dialog fragment.
Is there any alternative solution, like getting the activity, the instance or casting to something?
You don't need to call the static setColorPickerListener method. You can find your DialogFragment instance using findFragmentByTag method and then simply call your setColorPickerListener (non-static method).
public void showPickerDialog() {
DialogFragment newFragment = new PickerFragment();
newFragment.show(this.getSupportFragmentManager(), "dialogfrag1");
getSupportFragmentManager().executePendingTransactions();
// getting the fragment
PickerFragment df1 = (PickerFragment) getSupportFragmentManager().findFragmentByTag("dialogfrag1");
if (df1 != null) {
df1.registerListener(this);
}
}