I've been dabbling with MVP for android and have read from different sites about not passing an activity as argument into the presenter as it's anti-pattern but i have come across a problem where i need to use a third party method that requires the activity/context.
I've thought of using Dependency Injection, doing another abstraction or just pass it only to the method that requires it, rather than the constructor but i'm confused with what's better.
An example in Kotlin (Presenter)
fun Food(*should activity be passed here?*) {
var bar = Foo(activity).build
Stand(bar, callback{})
}
Would be great if someone can enlighten me on this.
I don't think any obvious reason why you should avoid passing Activity Context to Presenter. I heard that you should make presenter independent of any Android packages because you can reuse them for Desktop/TV or other platforms, but I don't think this will work.
If you need activity in your presenter you have several ways to do it
(my DI cases will use Dagger2 library but you can take a look at another ones):
Just pass activity in setter/constructor, to make it safer use WeakReference. This is how we do it in our project.
public class BasePresenter implements IPresenter {
protected VIEW view;
private WeakReference<FragmentActivity> activityWeakReference = new WeakReference<>(null);
#Override
public void onStart(FragmentActivity activity) {
activityWeakReference = new WeakReference<>(activity);
}
#Override
public void onStop() {
activityWeakReference.clear();
}
#Override
public void onViewCreated(Bundle savedInstanceState) {
}
public Optional<FragmentActivity> getActivity() {
return Optional.ofNullable(activityWeakReference.get());
}
public void getActivity(Action1<FragmentActivity> action) {
getActivity().ifPresent(action::call);
}
#Override
public void setView(VIEW view) {
this.view = view;
}
#Override
public boolean onBackPressed() {
return false;
}
#Override
public void navigationButtonClick() {
getActivity(Activity::finish);
}
#Override
public boolean onCustomActivityResult(int requestCode, int resultCode, Intent data) {
return false;
}}
You can provide your context to dagger modules like this.
You can use Dagger Android Injector. Personally I'm not a big fan of it but this should work. Read this article.
Do not forget to null your activity when presenter not needed anymore, because they are huge in memory so it will cause leaks.
If you want to work in a "clean way" you should always push the frameworks (android, third party libraries, data sources...) to the outer layer of your architecture (as far as possible from your business logic).
In your case I would create a "wrapper class" for the 3rd party library and the context and I would inject the interface of this class as a constructor parameter of your presenter.
In this way your presenter is clean and is not affected if you have to change the 3rd party library.
Here you can read more about clean architecture:
https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
https://github.com/android10/Android-CleanArchitecture
https://android.jlelse.eu/a-complete-idiots-guide-to-clean-architecture-2422f428946f
Related
So I need to get clipboard data in presenter. This means I need context. Unfortunately I don't have any knowledge of dependency injection if it is the only standard way.
I studied some solutions but they are stated as faulty solutions.
public class MainActivityPresenter implements MainActivityInterfaces.MainToPresenter {
MainActivityInterfaces.PresenterToMain main;
public MainActivityPresenter (MainActivityInterfaces.PresenterToMain main) {
this.main = main;
}
#Override
public void processUrl() {
String url = Utils.getClipboardData(context);
if (url.isEmpty()) {
} else {
}
}
}
And this is the method in Utils class
public static String getClipboardData (Context context) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
android.text.ClipboardManager cm = (android.text.ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE);
String result = cm.getText().toString();
return result==null?"":result;
} else {
ClipboardManager cm = (ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE);
ClipData.Item item = cm.getPrimaryClip().getItemAt(0);
if (item != null)
return item.getText().toString();
else
return "";
}
}
The right way, The presenter shouldn't know about any specific Android element, ClipboardManager or Context (ie: it should be pure Java). The clipboard logic should be taking place within the View and returning whatever it needs to the Presenter.
The ugly way, if you want to continue with this design, processUrl should take Context, presumably this method is being called from the view? So that is where context can be provided.
#Override
public void processUrl(Context context) {
String url = Utils.getClipboardData(context);
if (url.isEmpty()) {
} else {
}
}
In the view:
presenter.processUrl(getApplicationContext());
Another solution is to ask for context from the presenter to the view:
#Override
public void processUrl() {
final Context context = view.getContext();
String url = Utils.getClipboardData(context);
if (url.isEmpty()) {
} else {
}
}
//In the view
void Context getContext() {
return getApplicationContext();
}
At MVP, when the View propagates an event to the Presenter, it is very common for certain information to be sent to the Presenter. In your case, it would be quite logical to rename your method as follows:
#Override
public void processUrl(String url) {
if (url.isEmpty()) {
} else {
}
}
In this way, the View will be in charge of propagating the information along with the event. And you also keep in the Presenter the ignorance about the peculiarities of the View (Android things), something very important in the MVP pattern.
Good luck!
I know this won't help your question. But aside from MVP, MVVM is much robust and cleaner pattern that doesn't require as much boilerplate code as MVP does, so if you are still in a phase where you are starting a project and deciding or an architecture, I can, as a professional Android developer, only recommend MVVM. MVVM also does the state saves due to LiveData living inside ViewModels so you don't have to painfully add the ability to the presenter via saved instance bundles.
If you need DI in order to inject the context into the presenter (which isn't desirable, context shouldn't get into presenters ideally but sometimes it happens as it is harder to create entities that do the context required job in the presenter), and you are new and unexperienced in DI, then with Java you need to go with Dagger 2, which has a steep learning curve. Switching to Kotlin and then using Koin would be perhaps easier than learning Dagger 2, but if you are into it, try it. In any way it is a required to have DI in a professional projects.
If you don't want to use DI in order to pass the context into the presenter, then pass it into the constructor. I guess the Activity or Fragment is where you initialise your presenter (creating a new instance of it) and where you pass the 'view' concept into the presenter. If you need context in the presenter (again, not the best thing to do but ok, you are a new into patterns I understand), then just pass it into the constructor and set it in the presenter.
Using context in Presenter is not a good idea.
What if I need the Context?
Well, get rid of it. In cases like this, you should ask yourself why you need the context. You may need the context to access shared preferences or resources, for example. But you shouldn’t do that in the presenter: you should access to resources in the view and to preferences in the model. These are just two simple examples, but I can bet that the most of the times it is just a problem of wrong responsibilities.
You can pass url in constructor from View.
public class MainActivityPresenter implements MainActivityInterfaces.MainToPresenter {
MainActivityInterfaces.PresenterToMain main;
private String mUrl;
public MainActivityPresenter (MainActivityInterfaces.PresenterToMain main, String mUrl) {
this.main = main;
this.mUrl = mUrl;
}
#Override
public void processUrl() {
if (mUrl.isEmpty()) {
} else {
}
}
interface SomeView {
void onUrlProcessed();
}
}
In your MainActivity:
class MainActivity {
String url = Utils.getClipboardData(getContext());
}
You can refer other answers here .
Does the presenter having knowledge of the Activity / Context a bad idea in the MVP pattern?
Android MVP: safe use Context in Presenter
The suggested way to implement ViewModel is to expose the changing data by using LiveData objects to activities, fragments and views. There are cases, when LiveData is not an ideal answer or no answer at all.
The natural alternative would be, to apply the observer pattern to the ViewModel, make it an observable. When registering observers to the ViewModel, the ViewModel will hold callback references to notify the observers.
The documentation says, a ViewModel must not hold references to activities, fragments or views. The only answer to the question "why" I found is, that this may cause memory leaks. Then how about cleaning up the references to avoid memory leaks?
For views this is a difficulty. There is no defined moment, when the view goes away. But activities and fragments have a defined lifecycle. So there are places to unregister as observers.
What do you think? Is it valid to register activities as observers to ViewModels if you take care to always unregister them? Did you hit upon any valid information about this question?
I set a small reward for the best answer. It's not because I think it a recommended solution (as it does not work with views). I just want to know and extend my options.
public class ExampleViewModel extends ViewModel {
public interface OnEndListener {
public void onEnd();
}
private List<OnEndListener> onEndListeners = new ArrayList<>();
public void setOnEndListener(OnEndListener onEndListener) {
onEndListeners.add(onEndListener);
}
public void removeOnEndListener(OnEndListener onEndListener) {
onEndListeners.remove(onEndListener);
}
public void somethingHappens() {
for (OnEndListener onEndListener: new ArrayList<OnEndListener>(onEndListeners) ) {
onEndListener.onEnd();
}
}
}
public class ExampleActivity extends AppCompatActivity {
ExampleViewModel exampleViewModel;
ExampleViewModel.OnEndListener onEndListener;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
onEndListener = new ExampleViewModel.OnEndListener() {
#Override
public void onEnd() {
finish();
}
};
exampleViewModel = ViewModelProviders.of(this).get(ExampleViewModel.class);
exampleViewModel.setOnEndListener(onEndListener);
}
#Override
protected void onDestroy() {
super.onDestroy();
exampleViewModel.removeOnEndListener(onEndListener);
}
}
To ask "am I allowed..." is not really a useful question, IMO. The docs are clear that what you are suggesting is discouraged and why. That said, I expect that your code would probably work as expected and is therefore "allowed" (i.e. not prevented by a technical constraint).
One possible gotcha scenario: InstanceA of ExampleActivity is started and kicks off some long-running task on the ExampleViewModel. Then, before the task completes, the device is rotated and InstanceA is destroyed because of the configuration change. Then, in between the time when InstanceA is destroyed and a new InstanceB is created, the long-running task completes and your view model calls onEndListener.onEnd(). Except: Oh no! The onEndListener is null because it was cleared when InstanceA was destroyed and hasn't yet been set by InstanceB: NullPointerException
ViewModel was designed (in part) precisely to handle edge cases like the gotcha scenario above. So instead of working against the intended use of the ViewModel, why not just use the tools it offers along with LiveData to accomplish the same thing? (And with less code, I might add.)
public class ExampleActivity extends AppCompatActivity {
ExampleViewModel exampleViewModel;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
exampleViewModel = ViewModelProviders.of(this).get(ExampleViewModel.class);
exampleViewModel.getOnEndLive().observe(this, new Observer<Boolean>() {
#Override
public void onChanged(#Nullable Boolean onEnd) {
if (onEnd != null && onEnd) {
finish();
}
}
});
}
}
public class ExampleViewModel extends ViewModel {
private MutableLiveData<Boolean> onEndLive = new MutableLiveData<>();
public MutableLiveData<Boolean> getOnEndLive() {
return onEndLive;
}
public void somethingHappens() {
onEndLive.setValue(true);
}
}
Think of the LiveData in this case not as actual "data" per se, but as a signal that you can pass from your ViewModel to your Activity. I use this pattern all the time.
I am using the visitor pattern to abstract payment processing away from the UI code in android. I have some doubts on what i should pass into the visitor constructor inorder for the view to get a call back once its done processing the payment.
Let me show you what i have so far:
i am dealing with 2 payment systems, thus two payment strategies (brainTree and Stripe):
public class BrainTreePaymentStrategy implements IVisitable {
#Override
public void makePayment() {
}
#Override
public void accept(Visitor v) {
}
}
public class StripePaymentStrategy implements IVisitable {
#Override
public void makePayment() {
}
#Override
public void accept(IVisitor v) {
}
}
public interface IVisitable {
void makePayment();
void accept(IVisitor v);
}
public interface IVisitor {
//list out all the classes the visitor can visit now
void visit(StripePaymentStrategy stripePaymentStrategy);
void visit(BrainTreePaymentStrategy brainTreePaymentStrategy);
}
//now critical, lets create a real concrete visitor that can actually do the work:
public class PaymentStrategyVistor implements IVisitor {
#Override
public void visit(StripePaymentStrategy stripePaymentStrategy) {
//process the braintree payment here, but how to give call back to UI ?
}
#Override
public void visit(BrainTreePaymentStrategy brainTreePaymentStrategy) {
//process the braintree payment here, but how to give call back to UI ?
}
}
i am using uncle bob's clean architecuture so my network calls are through usecases and also im using mvp for my presentation layer so i have access to presenter and usecase if needed.
So again my question is regarding PaymentStrategyVistor class, what do you think if i passed in the presenter as a constructor parameter. i for example , could then call presenter.doBrainTreePayment("someToken"); i could do that in the visitors visit(BrainTreePaymentStrategy brainTreePaymentStrategy) method. is this how you all would do it ?
Your suggestion (passing the presenter to the constructor of each visitor) seems to be totally fine.
Looking from clean architecture perspective this all is fine as long as u do not violate the dependency rule. so if ur strategy and visitors live in the "interface adapter layer" u can easily pass the presenter. on the other hand if ur strategy/visitor belong to the "use cases layer" than passing the presenter would violate the dependency rule and u should not do it.
For a more detailed discussion on presenters in clean architecture see my blog post: https://plainionist.github.io/Implementing-Clean-Architecture-Controller-Presenter/
I struggled with some issues about design complex tasks with fragments
use. Fragments and asynchronous approach are quite new for me, so I think it will be better to describe my app.
Application
App works with GitHub API and has two screens: list of repositories and details about selected one. For retrieving data from json I use Retrofit and store it in SQLite. Little remark As I understood Retrofit can be used asynchronously but in case of additional work with DB it is better to use asynchronous approach for operations under DB also. In my case I'm checking internet connection: in case of absence I load data from DB. Otherwise I upgrade DB and then use it. Now I want to add fragments for different screen density support( usual master - detail workflow).
And my questions are
Where is the better place to run async tasks? Is it a right solution to make it in activity and then pass result to fragments?
What is the better solution for asynchronous processing? As I understood from my search about that, AsyncTask is deprecated but the easiest solution.
AsyncTask is a pain in the rear. Many beginners still seem to use it, but imho it's not worth learning it. Sooner or later you'll be bugged out by it because AsyncTask is error prone, has lots of caveats and tons of boilerplate code.
Retrofit does make it's calls asynchronously automatically, so you've got that covered already. Retrofit also plays very nice with RxJava which is I guess considered the way of doing asynchronous things on Android these days.
RxJava has a steeper learning curve initially than other patterns, but it's worth learning. If you got your database stuff working already, it won't be much work making ti asynchronous with Rx.
As for
Is it a right solution to make it in activity and then pass result to
fragments?
If you don't follow an MVP design approach, which is okay, in my opinion it's absolutely okay to do 'business logic' stuff in the Fragment and not let the Fragment call the Activity, then let the Activity get back to the Fragment. Whichever is easier for you and suits your app.
You can place your background thread anywhere, but make sure it is cancelled if the class is garbage collected, and don't keep references (Context, callbacks) in your thread.
public class MyActivity extends Activity {
private static interface OnDownloadThreadCompleteListener {
public void onDone(String data);
}
private static class DownloaderThread extends AsyncTask<Void, Void, String> {
private OnDownloadThreadCompleteListener mListener;
public DownloaderThread(OnDownloadThreadCompleteListener listener) {
mListener = listener;
}
#Override
protected String doInbackground(Void... args) {
// Do your network request here
return result;
}
#Override
public void onPostExecute(String data) {
if (mListener != null && !isCancelled()) {
mListener.onDone(data);
}
mListener = null;
}
#Override
public void onCancelled() {
mListener = null;
}
}
private DownloaderThread mThread;
private OnDownloadThreadCompleteListener mListener;
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
mListener = new OnDownloadThreadCompleteListener() {
#Override
public void onDone(String data) {
Fragment fragment = getFragmentManager().findFragmentByTag("fragment_git");
fragment.show(data);
}
}
mThread = new DownloaderThread(mListener);
findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mThread.execute(null, null, null);
}
}
}
#Override
public void onPause() {
super.onPause();
if (mThread != null) {
mThread.cancel(true);
}
mThread = null;
mListener = null
}
}
Is there any way in android to intercept activity method calls (just the standart ones, like "onStart. onCreate")?
I have a lot of functionality that must be present in every activity in my app, and (since it uses different types of activities (List, Preferences)) the only way to do it is to create my custom extensions for every activity class, which sucks :(
P.S. I use roboguice, but since Dalvik doesn't support code generation at runtime, I guess it doesn't help much.
P.S.S. I thought about using AspectJ, but it's too much of a hassle since it requires a lot of complications (ant's build.xml and all that junk)
The roboguice 1.1.1 release includes some basic event support for components injected into a context. See http://code.google.com/p/roboguice/wiki/Events for more info.
For Example:
#ContextScoped
public class MyObserver {
void handleOnCreate(#Observes OnCreatedEvent e) {
Log.i("MyTag", "onCreated");
}
}
public class MyActivity extends RoboActivity {
#Inject MyObserver observer; // injecting the component here will cause auto-wiring of the handleOnCreate method in the component.
protected void onCreate(Bundle state) {
super.onCreate(state); /* observer.handleOnCreate() will be invoked here */
}
}
You could delegate all the repetitive work to another class that would be embedded in your other activities. This way you limit the repetitive work to creating this object and calling its onCreate, onDestroy methods.
class MyActivityDelegate {
MyActivityDelegate(Activity a) {}
public void onCreate(Bundle savedInstanceState) {}
public void onDestroy() {}
}
class MyActivity extends ListActivity {
MyActivityDelegate commonStuff;
public MyActivity() {
commonStuff = MyActivityDelegate(this);
}
public onCreate(Bundle savedInstanceState) {
commonStuff.onCreate(savedInstanceState);
// ...
}
}
This minimalises the hassle and factorises all common methods and members of your activities. The other way to do it is to subclasse all the API's XXXActivty classes :(
Take a look at http://code.google.com/p/android-method-interceptor/, it uses Java Proxies.