I have two fragments (each a tab in a sliding tab activity) in my application. The two fragments have some methods which now are identical, I thought that I could abstract out those methods to follow the DRY (don't repeat yourself) principle. Is there any recommended way of doing this?
Is a util class with static methods a good way? Or should I create an abstract class "MyAbstractFragment" which has those methods and let the fragments extend this class?
For example.
public class MyCustomFragment extends Fragment {
protected LinearLayout linearLayout;
protected MyAdapter adapter;
//more common fields
void addButtonToFragmentView(final String btnText) {
final Button btn = new Button(getContext());
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
linearLayout.removeView(btn);
}
});
btn.setText(btnText);
linearLayout.addView(btn);
}
void upDateAdapterList(List<String> list){
//....
adapter.updateList(list);
}
}
Than my fragments could extend this class and set the properties and use the methods. But I also see that I as well could make a static util class just for the methods, like addButtonToFragmentView(final String btnText, Context context, final LinearLayout linearLayout) and upDateAdapterList(List<String> list, MyAdapter adapter)
Or is there a preferred way of doing this?
Yes, you can use abstract class like below:
public abstract class BaseFragment extends Fragment {
#Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public Context getContext() {
return this.getActivity().getApplicationContext();
}
protected abstract void addButtonToFragmentView(final String btnText);
protected abstract void upDateAdapterList(List<String> list){
}
then extends your new fragment with this base class.
code taken from:
https://github.com/spirosoik/AndroidArchitecturePadawans/blob/master/presentation/src/main/java/com/architecture/padawans/views/common/BaseFragment.java
We should try to follow composition over inheritance. May be you can have a dedicated UIFactory class which deals with dynamic creation of views, then you move your addButtonToFragmentView method to the UI factory and make if more generic.
void addButtonToView(final String btnText, final Context, final View parentView);
As far as upDateAdapterList is concerned you can create a BaseListFragment and move it there, so whoever is interested in using a fragment with List can extend this Fragment. Hence this follows Single Responsibility Principle.
Related
I have only one activity in my app. Before I just stored my views and dialogs static in the activity, so I could access them from anywhere. But I know that this is bad practice because it leads to memory leaks.
So I made them non-static, but now I need to have a reference to my activity deep down in my view hierarchy, to access the views and dialogs stored in the activity.
Example:
My MainActivity has a dialog called a and a custom view called b. How can the onClick method of b show the dialog a?
or in code:
public class MainActivity extends Activity {
private CustomDialog a;
private CustomView b;
#Override
protected void onCreate(Bundle savedInstanceState) {
a = new CustomDialog(this);
b = new CustomView(this);
}
}
public class CustomView extends Button implements OnClickListener {
public CustomView(Context context) {
super(context);
setOnClickListener(this);
}
#Override
public void onClick(View view) {
//wants to show dialog a
MainActivity.a.show(); //Not possible -> a is not static
mainActivity.a.show(); //<-- needs a reference of the activity
// but where from?
}
}
MainActivity mainActivity = (MainActivity) getContext(); won't work because getContext() is not always an activity context.
UPDATE:
I posted an answer below!
For some reasons StackOverflow only lets me accept my own answer in two days
I do not know what exactly your view hierarchy looks like.
I picture your problem for example as:
Activity A has a recyclerview R, now every viewholder H in R should be able to trigger some method in A.
In such a scenario it would be feasable to pass a reference of your activity to your recyclerview adapter and then the adapter passes it to the ViewHolder.
Which then uses it in the onClick method of your (viewholder's) view.
Here, you could use the "callback" pattern. There are many posts about this on stackoverflow, e.g. here.
So the implementation steps would be:
define interface
let your activity implement that interface
let your adapter take the interface as a constructor parameter and pass your activity. (in this example: you have to repeat the step with your viewHolder, pass the interface from the adapter)
use this interfaces method in the onClick method -> this will then trigger your activities method
The implementation depends on the actual hierarchy. If your other view is in a fragment, then you could also use a (shared) ViewModel.
According to your picture I was thinking of the callback-pattern approach first.
You could override onClick in MainActivity; there is probably no need to do it in the class definition itself.
public class MainActivity extends Activity {
private CustomDialog a;
private CustomView b;
#Override
protected void onCreate(Bundle savedInstanceState) {
a = new CustomDialog(this);
b = new CustomView(this);
b.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
a.show();
}
});
}
}
This is a very common pattern in android and I don't know what your view hierarchy looks like but it should work in most cases.
I am having trouble understanding why any class extending Button would need to implement View.OnClickListener. It makes much more sense to create listeners in activities or have MainActivity implement OnClickListener.
A few minutes a go there was an answer here that turned out to be correct.
I don't know why the author deleted it, but it had a link to this answer:
private static Activity unwrap(Context context) {
while (!(context instanceof Activity) && context instanceof ContextWrapper) {
context = ((ContextWrapper) context).getBaseContext();
}
return (Activity) context;
}
So everytime you need the activity you just can call Activity activity = unwrap(getContext());.
I don't know if it is really intended to do it that way or if it is a workaround, but it does its job (atleast in my case).
I have an activity with 3 fragments, currently I use ViewPager. I want to implement MVP and communicate between activity presenter and fragment presenters i.e:
Passing data from activity presenter to fragment presenters
Sending event from fragment presenters to activity presenter
...
But I don't know how to do it in official way. I can use BusEvent but I don't think it's a good practice.
Communication between fragments and activity or vice-versa can be done by using
nnn's answer or you could use ViewModel and LiveData witch provides a cleaner way and respect the lifecycle from fragments and activities which can save from writing a few lines of code in attempt to prevent a a non-visible fragment from receiving data on the background.
First you extend the ViewModel class, initialize the Livedata and some helper methods.
public class MyViewModel extends ViewModel {
private MutableLiveData<String> toFragmentA, toFragmentB;
private MutableLiveData<List<String>> toAllFragments;
public MyViewModel() {
toFragmentA = new MutableLiveData<>();
toFragmentB = new MutableLiveData<>();
toAllFragments = new MutableLiveData<>();
}
public void changeFragmentAData(String value){
toFragmentA.postValue(value);
}
public void changeFragmentBData(String value){
toFragmentB.postValue(value);
}
public void changeFragmentAllData(List<String> value){
toAllFragments.postValue(value);
}
public LiveData<String> getToFragmentA() {
return toFragmentA;
}
public LiveData<List<String>> getToAllFragments() {
return toAllFragments;
}
public LiveData<String> getToFragmentB() {
return toFragmentB;
}
}
Then you initialize the ViewModel on your activity.
public class MainActivity extends AppCompatActivity {
private ViewPager viewPager;
private TabLayout tabLayout;
MyViewModel mViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewModel = ViewModelProviders.of(this)
.get(MyViewModel.class);
viewPager.setAdapter(new Adapter(getSupportFragmentManager()));
}
}
reading the data in the fragments:
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mViewModel = ViewModelProviders.of(getActivity()).get(MyViewModel.class);
mViewModel.getToAllFragments().observe(this, new Observer<List<String>>() {
#Override
public void onChanged(List<String> s) {
myList.addAll(s);
//do something like update a RecyclerView
}
});
mViewModel.getToFragmentA().observe(this, new Observer<String>() {
#Override
public void onChanged(String s) {
mytext = s;
//do something like update a TextView
}
});
}
to change the values of any of the live datas you can use one of the methods in any of the fragments or in the activity:
changeFragmentAData();
changeFragmentBData();
changeFragmentAllData();
Whats happing behind the scenes:
when you use mViewModel = ViewModelProviders.of(this).get(MyViewModel.class) you are creating a n instance of ViewModel and binding it to the lifecycle of the given activity of fragment so the view model is destroid only the the activity or fragement is stopped. if you use mViewModel = ViewModelProviders.of(getActivity()).get(MyViewModel.class)you are bindig it to the lifecycle if the parentactivity`
when you use mViewModel.getToFragmentA().observe() or mViewModel.getToFragmentB().observe() or mViewModel.getToAllFragments().observe() you are connecting the LiveData in MyViewModel class to the given fragment or activity an the value of the onChange() method is updated in all the classes that are observing the method.
I recomend for personal expirience a bit of research about Livedata end ViewModel which ou can on youtube or this link
As per my understanding, for your UseCase, suppose ActivityA have a viewPager having 3 Fragments(FragmentA, FragmentB, FragmentC).
ActivityA have ActivityPresenterA
FragmentA have FragmentPresenterA
As per MVP, FragmentPresenterA should be responsible for all the logical and business flows of FragmentA only and should communicate with FragmentA only. Therefore, FragmentPresenterA can not directly communicate with ActivityPresenterA.
For communication from Fragment to Activity, presenter should not be involved and this should be done as we would communicate in non-MVP architecture, i.e. with the help of interface.
Same applies for Activity to Fragment communication.
For communication between Activity and Fragment read here
You can use one presenter for that case.
Used your Activity Presenter to get all the data that your fragments need.
then create an interface class and implement it to your fragments.
For example:
Create a public interface for your PageAFragment (this interface will the bridge of your data from activity to fragment). and use the method of your interface to handle the result from your presenter to view.
This is the example of interface class that I created for received data. for the parameter you can choose what you want it depends on your need, but for me I choose model.
public interface CallbackReceivedData {
void onDataReceived(YourModel model);
}
In MainActivity Class check the instance of fragment that attached into your activity. put your checking instance after you commit the fragment.
public class MainActivity extends AppCompatActivity{
private CallbackReceivedData callbackReceivedData;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//after commit the fragment
if (fragment instanceof PageAFragment){
callbackReceivedData = (CallbackReceivedData)fragment;
}
}
//this is the example method of MainActivity Presenter,
//Imagine it, as your view method.
public void receivedDataFromPresenter(YourModel model){
callbackReceivedData.onDataReceived(model);
}
}
I assumed that the receivedDataFromPresenter is the received method of our view and get data to presenter.
And now we will pass the data from presenter to callbackReceivedData
In PageAFragment implement the CallbackReceivedData and Override the onDataReceived method. Now you can passed the data from activity to your fragment.
public class PageAFragment extends Fragment implements CallbackReceivedData{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onDataReceived(YourModel model) {
}
}
Note: Alternative way, you can use Bundle and pass the data with the use of setArguments.
If you want to send Event from Fragment to Activity you can follow this Idea.
Create an Interface class and implement it to your MainActivity and Override the method from interface to your activity, for My case I do it something like this.
Here's my CallbackSendData Class.
public interface CallbackSendData {
void sendDataEvent(String event);
}
Implement CallbackSendData interface to your MainActivity and Override the sendDataEvent method.
public class MainActivity extends AppCompatActivity implements CallbackSendData{
private CallbackReceivedData callbackReceivedData;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//after commit the fragment
if (fragment instanceof PageAFragment){
callbackReceivedData = (CallbackReceivedData)fragment;
}
}
//this is the example method of MainActivity Presenter,
//Imagine it, as your view method.
public void receivedDataFromPresenter(YourModel model){
callbackReceivedData.onDataReceived(model);
}
#Override
public void sendDataEvent(String event){
//You can now send the data to your presenter here.
}
}
And to your PageAFragment you need to use attach method to cast your interface. The attach method called once the fragment is associated with its activity. If you want to understand the lifecycle of fragment just click this link: https://developer.android.com/reference/android/app/Fragment.html.
public class PageAFragment extends Fragment implements CallbackReceivedData{
private CallbackSendData callbackSendData;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onDataReceived(YourModel model) {
//Received the data from Activity to Fragment here.
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup
container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.PagerAFragment, container,
false);
}
#Override
public void onViewCreated(View view, #Nullable Bundle
savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Button Eventbutton;
Eventbutton = view.findViewById(R.id.event_button);
Eventbutton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
callbackSendData.sendDataEvent("send Data sample");
}
});
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try{
callbackSendData = (CallbackSendData) context;
}catch (ClassCastException e){
e.printStackTrace();
}
}
}
And now you can use the CallbackSendData to send the data from activity to fragment.
Note: It's much easier if you are using Dependency Injection to your project, you can use Dagger2 library.
Goodluck.
To communicate between a Fragment and an Activity (whether between their presenters or their classes), you need an interface that your activity implements (like ShoppingInteractor).
This way you can call ((ShoppingInteractor)getActivity()).doSomething() in the fragments. If you want your activity's presenter to handle the task, you need to call the presenter in the doSomething inside the activity.
You can do the same with the fragments with another interface and call the fragment's interactor inside the activity.
You can even have a Presenter getPresenter() inside these interfaces to have access to the actual presenter. (((ShoppingInteractor)getActivity()).getPresenter().sendData(data)). Same goes for the fragments.
If you want to use MVP, the first step is to create one presenter for each View, I mean, If you have 3 fragments, then would have 3 presenters. I think that is a bad idea to create one presenter for 4 views (activity and 3 fragments).
Dynamic data:
Here is an example using rxjava2, dagger2 and moxy.
Conditionalities:
Presenters do not depend on the life cycle of the view
One presenter - one view. The views do not share the presenters among themselves and one view has only one presenter.
The solution is similar to the EventBus, but instead uses Subject with a limited lifetime. It is in the component that is created when the activity starts and is destroyed when it exits. Both activity and fragments have an implicit access to it, they can change the value and respond to it in their own way.
Example project: https://github.com/Anrimian/ViewPagerMvpExample
Static data:
Just use arguments in the fragment and that's it.
Can someone please tell me if I'm solving this correctly or if I should go another route?
This is a simplified example: I have 1 Activity and 2 Fragments. Each Fragment has a button that when clicked, relays the click back to the Activity and a Toast pops up within the Activity.
I know that a Fragment communicates with an Activity through an interface. But what If I have multiple Fragments that have a similar Interface. For example, here both Fragments use an onClick type of interface to communicate back to the Activity
static interface OnClickedListener{
public void buttonClicked(View v);
}
Is it better to
A) Create a separate Interface class and attach it within both Fragments. For example Fragment 1:
public class Fragment1 extends Fragment implements OnClickedListener{
private OnClickedListener clickedInterface;
public Fragment1() {
// Required empty public constructor
}
#Override
public void buttonClicked(View v) {
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.clickedInterface = (OnClickedListener)activity;
}}
Fragment 2:
public class Fragment2 extends Fragment implements OnClickedListener{
private OnClickedListener clickedInterface;
public Fragment2() {
// Required empty public constructor
}
#Override
public void buttonClicked(View v) {
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.clickedInterface = (OnClickedListener)activity;
}
OR
B) Create individual Interfaces unique to the specific Fragment and implement those in the MainActivity instead of the one Interface like mentioned above. Thank you.
First Create your custom fragment which is in implement interface.
public class CustomFragment extends Fragment implements OnClickedListener{
public OnClickedListener clickedInterface;
#Override
public void buttonClicked(View v) {
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.clickedInterface = (OnClickedListener)activity;
}
}
Now, you can add in every fragment
(i) Fragment 1
public class Fragment1 extends CustomFragment {
......
}
(ii) Fragment 2
public class Fragment2 extends CustomFragment {
......
}
I wouldn't let the details of any Views contained in a Fragment leak out into the Activity.
Better interfaces would be based on the semantic action involved with the button press, for example createThing() or deleteThing().
The Activity shouldn't really care which View was clicked to initiate the action just that the action needs to happen. When happens to your interfaces if you move the button to the menu, or somewhere else not associated with a View.
If you create interfaces like this, the question of creating copies for different Fragments disappears.
There is a code principal called SOLID. "I" states for https://en.wikipedia.org/wiki/Interface_segregation_principle. It is a good practice to
You should not make universal interface or large ones, you need to create interface that can be "readable" and "understandable" for everyone by interface name and its method names like "articleSelected" or "loginProcessing(String loginName)" etc
I want to know if Android support the possibility to make a container with some static components and just include my activities in it.
I have a sliding menu with some onClickListener events and I don't want to set these events for each activity.
If I understood you correctly, you have some functionality that is common to several Activities, and you don't want to repeat the same code in all of them. Instead, you want to do that in one place.
One way to achieve this is to create a superclass activity, place your common code in that activity, and then extend it with your other activities. For example,
public class BaseActivity extends Activity implements OnClickListener {
private Button button;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
button = (Button) findViewById(R.id.my_button);
button.setOnClickListener(this);
}
...
#Override
public void onClick(View view) {
int id = view.getId();
switch(id) {
case R.id.my_button:
// perform action
break;
}
}
}
Then you extend it as
public class Activity1 extends BaseActivity {
/...
}
public class Activity2 extends BaseActivity {
/...
}
public class Activity3 extends BaseActivity {
/...
}
I am not exactly sure I understand your question, can you perhaps elaborate some more? maybe even post some sample code that you are using currently.
From what I can tell you should be able to achieve what you want by making your own CustomActivity
public class CustomActivity extends Activity {
//put your slidingmenu stuff here
}
Then inside all of the other Activities where you want to use that shared piece do it like this:
public class AnotherActivity extends CustomActivity {
//...
}
with extends CustomActivity instead of the usual extends Activity
This is how i solved the problem:
First thing i did is creating my main class wich will host common code.
for example :
public abstract class main extends activity(){
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResourceId());
// Your common code here
}
protected abstract int getLayoutResourceId();
}
Then all what you need is to extend this class in your activity:
public class HelloActivity extends main{
#Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_hello);
super.onCreate(savedInstanceState);
//make sure to put setcontentview before super.oncreate
}
#Override
protected int getLayoutResourceId() {
return R.layout.activity_hello;
}
}
All the activities needs to be registered in the Manifest. For the common things, e.g. Slide menu, you can use Fragment for the slide menu.
I'm writing an application in which i have a set of code which i want to be available in all of my Activities and ActivityGroups. However, to achieve this, I have extended my activities as:
//custom Activity
public abstract class BaseActivity extends Activity
//custom ActivityGroup
public abstract class BaseActivityGroup extends ActivityGroup
//implemented activities in my app
public class PickUser extends BaseActivity
//and
public class Home extends BaseActivityGroup
Now the thing is, whatever the custom code i write in BaseActivity, I have to write the same in BaseActivityGroup too (as in current implementation). This is prone to code-sync problems and i believe not a good technique.
So, how can i make my extensions in such a way that I only write custom code in BaseActivity and my BaseActivityGroup extends ActivityGroup - which is conceived from BaseActivity class?
If i observe how android does this, so the ActivityGroup in android extends Activity class. And I also want to write my custom ActivityGroup class (known as BaseActivityGroup) that actually extends BaseActivity (which is an extended Activity).
Any ideas/suggestions?
First of all ActivityGroups are bad and should not be used. They are deprecated and it is preferred to use a single activity with multiple fragments.
If you must use an activitygroup you are probably best of by implementing a delegate pattern.
Create a delegate that handles all the common methods such as onCreate, onResume and use that in the bases. In this example I save a reference to the activity in the delegate. This circular referencing might not be the pretties. An alternative is to pass on the activity to the methods in the delegate.
public class ActivityDelegate() {
private Activity mActivity;
public ActivityDelegate(final Activity activity) {
mActivity = activity;
}
public void onCreate(final Bundle savedInstanceState) {
// Do stuff.
}
}
public abstract class BaseActivity extends Activity {
private ActivityDelegate mDelegate = new ActivityDelegate(this);
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDelegate.onCreate(savedInstanceState);
}
...
}
public abstract class BaseActivityGroup extends ActivityGroup {
private ActivityDelegate mDelegate = new ActivityDelegate(this);
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDelegate.onCreate(savedInstanceState);
}
...
}
Add an extra final class, called Base.
This one will only contain methods to be called by the other Base classes, such as for instance:
public static boolean createOptionsMenu(final Menu menu,
final MenuInflater inflater) {
inflater.inflate(R.menu.main_menu, menu);
return true;
}
Then, in your BaseActivity and BaseActivityGroup classes, you would call:
#Override
public final boolean onCreateOptionsMenu(final Menu menu) {
return Base.createOptionsMenu(menu, getMenuInflater());
}
Hope it helps!
Just Extend everything to BaseActivity including BaseGroupActivity as everything is a child of Activity in android
you can put your login in a separate file under a method. now call the same method from both BaseActivity and BaseActivityGroup if you need activity instance in file . pass context through constructor