Managing Multiple Fragments With ONE Interface - java

I have One Activity and Three Fragments. The application launches and Fragment 1 is visible. I click on a button. The Fragment communicates with the Activity through the following Interface and launches Fragment 2:
public OnClickedListener listener;
static interface OnClickedListener{
public void buttonClicked(View v);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
Activity a;
if (context instanceof Activity){
a=(Activity) context;
this.listener = (OnClickedListener)a;
}
}
...
playBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
listener.buttonClicked(v);
}
});
My MainActivity implements Fragment1.OnClickedListener, receives the data and launches Fragment 2.
Now I'm in Fragment 2. I want to click on a button and launch Fragment 3. I know that I can do that by implementing YET ANOTHER interface in Fragment 2 to then communicate to the Activity and say "Hey Launch Fragment 3". So now My Activity looks like this
MainActivity implements Fragment1.OnClickedListener,Fragment2.OnClickedListener
That's all fine but let's say that I have 20 Fragments. I don't want to have 20 interfaces implemented in my Main Activity. Is there a way to create and use a single interface to communicate between each individual Fragment and the Activity. How would that be implemented? Thank you.

You can. Create only an interface (I recommend you to create it in a separate file):
interface OnClickedListener {
void buttonClicked(Fragment fragment, View v);
}
The onButtonClicked() method accepts also a Fragment instance when a Button is clicked:
playBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// You can't use just "this", pass the class instance.
listener.buttonClicked(MyFragmentClassName.this, v);
}
});
In your Activity you will have so:
public class MainActivity extends Activity implements MainActivity.OnClickedListener {
#Override
public void buttonClicked(Fragment fragment, View v) {
// Check if the Fragment instance, or tag, or the info that you use to recognize it.
}
}

Create one nested interface in Activity or in separate file instead of nested interfaces in Fragments.

Related

Crashing when an Intent is called inside a Recycler View Adapter

public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
holder.parentCard.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent=new Intent(context,DetailedView.class);
context.startActivity(intent);
}
});
The Intent is not passing to the other activity when the cardView is clicked.
Recycler view doesnt have current activity context, so while recycler adapter initial time you need to pass current activity of context.
check context is null or not
check DetailedView activity registered manifest or not
another war to achieve intent interface concept is there
refer this tutorial better https://www.geeksforgeeks.org/android-recyclerview/
There are multiple reasons to this crash since you have not provided the Crash logs/stacktrace.
The context provided is not an Activity context in which case you need to add the Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK to your Intent's flags.
It is possible the crash is in the DetailedView Activity due to probably a NullPointerException or it is not added to AndroidManifest.xml
This is not the right way.
First of all crate interface callback
public interface ItemClick {
void onItemClick(int position);
}
Then calling this interface method inside adapter class and passing through the constructor and finally
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (settingsItemClick!=null)
{
int pos=getAdapterPosition();
if(pos!=RecyclerView.NO_POSITION)
{
settingsItemClick.onItemClick(pos);
}
}
}
});
3.Then inside activity implements interface method.

How can I send data from a tablayout fragment to the main activity

I have a listview in my tablayout. When I click on one of these elements, I want that element to be added to a list in the mainactivity. So that I can display this list on another fragment.
You can use interface when clicked on item
Create interface like this in one fragment
public interface OnClickListener{
void onClick(Item listItem);
}
and in fragment create object like:
OnClickListener onClickListener;
and in onAttach assign the object
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
onClickListener= (onClickListener) context;
} catch (ClassCastException e) {
}
}
and then implement this listener in your MainActivity
public class MainActivity extends AppCompatActivity implements onClickListener{}
and on button click in fragment pass the data to your MainActivity
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
onClickListener.onClick(YOUR_ITEM);
}
});

Change BottomNavigationView with a button inside a RecyclerView Item

The application has a BottomNavigationView for navigating between fragments. One of the Fragments related to the BottomNavigationView has a Fragment as a child containing a RecyclerView and each RecyclerView Item has a Button attached to it.
I would need to navigate the BottomNavigationView to another Fragment with an OnClick of the Button (Highlighted with the red) inside the RecyclerView Item. I have tried different ways but I have not gotten it to work so far.
The Click is handled inside the Adapter of the RecyclerView. (The code inside the OnClickListener is just to clarify what I am trying to do.)
#Override
public void onBindViewHolder(#NonNull LocationsViewHolder holder, int position) {
...
holder.showMarkerButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
BottomNavigationView navigation = (BottomNavigationView)v.findViewById(R.id.nav_view);
navigation.setSelectedItemId(R.id.navigation_map);
}
});
}
The easiest way is created an interface implemented by your Activity
First create an interface that i called OnItemClick :
public interface OnItemClick{
void onItemClick();
}
Following on your activity implement this interface like below :
public class MainActivity extends AppCompatActivity implements OnItemClick{
/*
rest of your code
*/
#Override
public void onItemClick() {
navigation.setSelectedItemId(R.id.navigation_map);
}
}
On your Fragment you need to pass the Activity into your Adapter
YourAdapter adapter = new YourAdapter(requireActivity());
And on your adapter you need to initialize the interface like below :
OnItemClick listener;
public YourAdapter(Activity activity) {
listener= ((MainActivity) activity);
}
And finaly to call the method on your activity just call it like below
#Override
public void onBindViewHolder(#NonNull LocationsViewHolder holder, int position) {
...
holder.showMarkerButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
listener.onItemClick();
}
});
}

How to call a method in a fragment through the onClick in another fragment?

I am building an app with 2 fragments. The 1st fragment has an ImageView and 2 TextViews and the 2nd fragment has a set of buttons and EditTexts. In the 2nd fragment, I have a button called "Save". When this button is clicked, I want to download the image inside my 1st fragment to my device folder (The ImageView, the URI, the bitmap and canvas objects are all in the 1st fragment).
Because fragments can't communicate with one another directly, I was thinking of doing this with an interface. What I did was:
I've declared my interface in the 2nd fragment
Applied the logic of the interface's method in the MainActivity (which is the shared activity between the 2 fragments)
Fed the necessary parameters for the method in the 1st fragment.
Didn't work
But I don't think that this was the correct order so it's no surprise that it didn't work. How do I apply the interface in a way that a button click in the 2nd fragment downloads the image in the 1st fragment?
You could try one of these three options:
1.) Using callbacks to communicate via the activity
As shown in this article, you can define an interface in fragment 2 which is then called when the button is clicked. Your activity (which holds fragment 2) provides an implementation for that interface in which the activity calls a method in fragment 1 for downloading the image. For example:
Fragment 1 providing the download method
public class Fragment1 extends Fragment {
OnButtonClickedListener mCallback;
public void startImageDownload() {
// Download the image
}
// ...
}
Fragment 2 defining and calling the interface
public class Fragment2 extends Fragment {
OnButtonClickedListener mCallback;
// Some kind of init method called by onCreate etc.
private void init() {
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Call the listener if present
if(mCallback != null) {
mCallback.onButtonClicked();
}
}
});
}
public void setOnButtonClickedListener(Activity activity) {
mCallback = activity;
}
// Container Activity must implement this interface
public interface OnButtonClickedListener {
public void onButtonClicked();
}
// ...
}
The Activity reacting on the Button click and calling the download method
public static class MainActivity extends Activity implements Fragment2.OnButtonClickedListener {
Fragment1 mFragment1;
#Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof Fragment2) {
// Register the listener
Fragment2 fragment2 = (Fragment2) fragment;
fragment2.setOnButtonClickedListener(this);
} else if (fragment instanceof Fragment1) {
// Keep a reference to fragment 1 for calling the "download" method
mFragment1 = (Fragment1) fragment;
}
}
#Override
public void onButtonClicked() {
// Handle the button click
mFragment1.startImageDownload();
}
}
This way you avoid linking the fragments together, instead you have the activity beeing a loose "connection" between fragment 1 and fragment 2.
This is just an exmple, i did not had time to test it. But i hope it helps.
2.) Using a local broadcast
I would recommend using the LocalBroadcastManager for sending a broadcast in fragment 1 (that the button was clicked) and receiving it in fragment 2 (downloading the image). Here is a great article about local broadcasts.
3.) Another option is to use ViewModel
The ViewModel was recently introduced by the new Android Jetpack and "is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations." (from ViewModel Overview).
It can also be used to share data between two fragments: Your activity basically holds the ViewModel and the fragments (inside that activity) can get access to it by calling: ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
I think your scenario you could use Observers or some kind of LiveData to react to the button-click via a ViewModel.
Thanks to #Elletlar for helping me improve my answer.

Presenter Instructing Current Activity to Inflate Next Activity; Violation of MVP Rules or Not

Considering the fact that in Android, you need a package context to be able to inflate the next activity from the current activity using an intent. However, with the MVP, the presenter is meant to be a pure Java class that is not glued to dependencies like the Android framework itself.
Is it proper to implement two methods in the view; One that asks the presenter to show a new view showNew(IView activity) and then the presenter authorizes this request by passing the new view back to the current view and asking it to use its (activity) context to inflate the requested next view.
In other words, the presenter is still the one who orders the creation of the next view, just that it does it through the context of the current view.
See Sample Code below:
View Interface
public interface IBaseView{
void showAnother(Class nextActivity);
}
Presenter Interface
public interface IBasePresenter {
void showNew(IBaseView nextActivity);
}
Concrete Presenter
public class Presenter implements IBasePresenter {
IBaseView view;
public Presenter(IBaseView view){
this.view = view;
}
#Override
public void showNew(IBaseView nextActivity) {
view.showAnother(nextActivity.getClass());
}
Android Activity
public class SampleActivity extends AppCompatActivity implements IBaseView{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
}
public void onClick(View view){
Presenter presenter = new Presenter(this);
presenter.showNew(new SampleNextActivity());
}
#Override
public void showAnother(Class nextActivity) {
Intent intent = new Intent(this, nextActivity);
startActivity(intent);
}
}
Android Next Activity
public class SampleNextActivity extends AppCompatActivity implements IBaseView {
//activity details here
}
In Summary
View creates the next view, but only at the instruction of the presenter. Because the presenter needs the context of the current view to be able to inflate a next view.
Question
Is this approach in any way violating the MVP methodology
If (1) is a violation or not clean enough, is there a better way to make the presenter create the views (without having knowledge of the Android Context Class) ?
In MVP pattern, persenter tells view what to do. The way you are passing the next activity class to the presenter, and then presenter send that activity class back to view is unnecessary IMO. Why not the presenter tells the view to navigate to which activity without knowing the activity class itself? I mean something like this:
Android Activity
public class SampleActivity extends AppCompatActivity implements ISampleView {
// ...
public void onClick(View view){
ISamplePresenter presenter = new SamplePresenter(this);
presenter.buttonClicked();
}
#override
public void navigateToNextView() {
Intent intent = new Intent(this, nextActivity);
startActivity(intent);
}
}
Concrete Presenter
public class SamplePresenter implements ISamplePresenter {
ISampleView view;
public Presenter(ISampleView view){
this.view = view;
}
#Override
public void buttonClicked() {
// check if user can go to next activity
view.navigateToNextView();
}
}
You can have a method in your view which its job is to navigate to a specific view, i.e. NextActivity. If you have more acitivities, you can define a method for each one. When user clicks on a button, you tell the presenter that user clicked on a button. Then you can perform some logical operation after button is clicked in SamplePresenter#buttonClicked() and then tell the view to navigate to the NextAcitivity. This approach is cleaner in my opinion since you do not need to pass activity class itself to presenter.

Categories