Activity interceptor - android

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.

Related

Am I allowed to observe a ViewModel, if I clean up the back references?

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.

Is it possible to avoid unnecessary injection in parent class?

Given the following example:
class CustomView extends View {
#Inject
SomeObject mObject;
#Override
protected void onFinishInflate() {
super.onFinishInflate();
getApplicationComponent().inject(this);
}
}
class SecondaryCustomView extends CustomView {
#Inject
AnotherObject mAnotherObject;
#Override
protected void onFinishInflate() {
super.onFinishInflate();
getApplicationComponent().inject(this);
}
}
Both custom views may be used on a layout independently. The second is just a bit more specialized than the first one.
As you can see, both have fields to inject and both need to call inject(). The problem is that when SecondaryCustomView calls its inject(), Dagger injects an instance of AnotherObject and an instance of SomeObject. After the call to super.onFinishInflate(), it creates a second instance of SomeObject. This is not a problem per se, but we are at least creating unnecessary objects.
Is there a way to avoid this? Some way to tell Dagger that a child class has been injected, so ignore the parent injection?
As an example, the Component looks like this:
#Component(...)
public interface AppComponent {
void inject(CustomView);
void inject(SecondaryCustomView);
}
There's no way to do this in Dagger, but you can do it yourself.
To agree with and expand on your point in the comments:
yes it is needed. Dagger does not inject the child objects if we use injection on parent only. however it injects the parent objects if it is called from the child.
That's correct, and noted in "A note about covariance": Though inject(Foo) can accept an instance of Foo or any of its subclasses, Dagger's a compile-time framework; inject(Foo) will not be generated to inject the fields belonging to arbitrary subclasses of Foo because, well, that's impossible to know at compile time. This can be a little surprising, particularly if your Component has both inject(Foo) and inject(FooSubclass) as you have for CustomView and SecondaryCustomView here: With names injectCustomView and injectSecondaryCustomView it would be obvious that only the former is callable from within Foo.
Aside from simply setting an injectedAlready boolean field as a flag, one technique is to create an overridable method, which does not call its superclass implementation:
class CustomView extends View {
#Inject
SomeObject mObject;
#Override
protected void onFinishInflate() {
injectMe();
super.onFinishInflate();
}
protected void injectMe() {
getApplicationComponent().inject(this); // inject(CustomView);
}
}
class SecondaryCustomView extends CustomView {
#Inject
AnotherObject mAnotherObject;
#Override
protected void onFinishInflate() {
super.onFinishInflate();
// ...
}
/** Despite looking identical, the JVM can call the more-specific overload here. */
#Override protected void injectMe() {
getApplicationComponent().inject(this); // inject(SecondaryCustomView)
}
}
If you're looking for a similar solution for Activity and Fragment classes, you can use dagger.android; the built-in mechanism there uses the runtime type of the class to dynamically fetch the right AndroidInjector from a Map. However, that solution doesn't support View at the moment, so this is as close as you can get for your specific case.

Should I create BaseActivity/Presenter and View in Mosby MvP concept?

I'm trying to understand concept of MvP design pattern. I mean, I get it, its quite easy. The main problem is optimal implementation. I tried to make my own BaseActivity, BasePresenter and BaseView just to extract part of a joint from all of my activities, I've done this this way:
BaseActivity
public abstract class BaseActivity<T extends BasePresenter<? extends IBaseView>> extends FragmentActivity implements IBaseView {
protected T presenter;
private ActivityConfig activityConfig;
#Override
final protected void onCreate(Bundle savedInstanceState) {
activityConfig = getConfig();
super.onCreate(savedInstanceState);
presenter = createPresenter();
setContentView();
initLibraries();
prepareView(savedInstanceState);
addFragments();
}
protected abstract ActivityConfig getConfig();
protected abstract T createPresenter();
protected abstract void prepareView(Bundle savedInstanceState);
protected abstract void addFragments();
private void setContentView(){
View root = View.inflate(this, activityConfig.layoutId, null);
setContentView(root);
}
private void initLibraries() {
ButterKnife.bind(this);
Timber.plant(new Timber.DebugTree());
}
#Override
public BaseActivity getCurrentContext() {
return this;
}
#Override
public T getPresenter() {
return presenter;
}
}
BasePresenter
public abstract class BasePresenter<T extends IBaseView> {
public abstract void loadData(boolean refresh);
}
BaseView
public interface IBaseView {
BaseActivity getCurrentContext();
BasePresenter getPresenter();
}
It works fine but I feel like this is bad designed so I want to use Mosby instead. The problem is that all of the tutorials don't touch aspect of base classes, they just use Mosby's ones as base (with is bad I suppose? couse I have to duplicate my code (Butterknife.bind() for example). So can you guys give me some good designed quickstart classes for Mosby MVP or give me some tips how should I divide my project? Thanks!
So I see two possibilities:
You could extend from Mosby's MvpActivity as your base class and add your staff like initView(), initLibraries() etc. So that BaseActivity<P extends BasePresenter<? extends BaseView>> extends MvpActivity<P> implements BaseView. Then MyFooActivity extends BaseActivity<FooPresenter>. So you include Butterknife once in BaseActivity and it should work. However, you might have to duplicate that code like Butterknife.bind()` for Fragments, as Activity and Fragments obviously don't have the same super class. I will show you how to solve that above.
Do the other way around: Integrate Mosby's functionality into your BaseActivity. Mosby is build with the principle of "favor composition over inheritance". So what does this actually mean? Mosby offers a ActivityMvpDelegate. As the name already suggests this delegate does all the work of instantiating Presenter etc. But instead of inheriting from MvpActivity you use this delegate and invoke the corresponding delegate methods. Actually Mosby's MvpActivity is doing exactly that if you have a look at the source code. So instead of extending from Mosby'sMvpActivity you simply use MvpActivityDelegate in your BaseActivity.
So what about duplicating code like Butterknife.bind() i.e. in Activity and Fragment. Well, Mosby can share his code like instantiating Presenter etc. between Activity and Fragment because both use the mosby delegate.
So you could apply the same principle: You could put the shared code into a delegate and call the delegate from both, activity and fragments.
The question is: is it worth i.e. Butterknife.bind() is just one single call. You would also have to make one single call yourDelegate.doSomething() ...
But if you have to reuse "critical code" between activity and fragments then favor composition like Mosby does.
If you know that you are only working with Activites then extending from Mosby's MvpActivity would also be a good option as described in 1. solution.
I just wanted to add to sockeqwe's first answer.
It is perfectly fine to create your own base class where it makes sense. It's also pretty straightforward.
For example, I needed to create a base Fragment with some default behavior. All you need to do is duplicate the base generic type signature and pass it along to the base class.
For example:
public abstract class MyBaseFragment<V extends MvpView, P extends MvpPresenter<V>> extends MvpFragment<V, P>

Dagger 2 - how to inject only to base activity/fragment

I am studying a Dagger 2 from many sources such as this one: http://fernandocejas.com/2015/04/11/tasting-dagger-2-on-android/
but I still haven't found an answer to my question.
I work on quite complex application with tens of fragments and several activities in which I want to use DI (dagger 2). For all of those fragments and activities I have one BaseActivity and one BaseFragment. However, as far as I read and tried, in order to use #Inject in my let's say MainActivity, I have to specify it in Component interface and also invoke getApplicationComponent().inject(this) in onCreate method. When I do this for BaseActivity only, #Inject annotated fields in MainActivity is never injected. And what is even worse, I do not find out about that until that specific part of code is executed and NPE is thrown.
So far it is a deal breaker for me, because this can be source of many crashes. I would need to specify tens of fragments and activities in Component interface and not forget to call inject in each onCreate method.
I would be very glad to hear any solution to this since I would really like to use DI..
code example:
#Singleton
#Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
void inject(BaseActivity baseActivity);
Analytics analytics();
}
public class BaseActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.getApplicationComponent().inject(this);
}
}
public class MainActivity extends BaseActivity {
#Inject
Analytics analytics;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
analytics.log("event1"); // THROWS NPE!
}
}
You can not inject properties in your subclass by injecting the super (since dagger2 works at compile time and there is no way to dynamically check subclasses for annotated properties.)
You can move analytics up to the super, then it will be injected there. To inject annotated fields in your subclass you will have to call the injection there again.
You can make an abstract method in your baseclass e.g. inject(App app)where you just handle the injection. That way you can't 'miss' it.
As stated in the official documentation:
While a members-injection method for a type will accept instances of its subtypes, only Inject-annotated members of the parameter type and its supertypes will be injected; members of subtypes will not.
move the
#Inject
Analytics analytics;
to your BaseActivity class, the Analytics object is initialized in the superclass and is inherited by sub-classes automatically, therefor u wouldn't get null any more.
public class MainActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
analytics.log("event1");
} }

Global error handling in Android application with Roboguice

I'm working on a Android app which uses the Roboguice dependency injection framework.
So most of the time we extend RoboActivity, RoboListActivity and similar.
Now I would like to introduce some sort of global error handling which will show some alert or a error activity in case the application crashes.
I have done this before by implementing a base activity like this:
public class BaseActivity extends Activity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Thread.setDefaultUncaughtExceptionHandler(new GeneralError(this));
}
where I define the default exception handler and all other activities then derived from this one.
Now I'm wondering how this is achieved with Roboguice?
Here is some rough psuedo code that should get you started. It uses the roboguice events to make some of these cross cutting concerns a little easier.
public class GlobalErrorHandler {
// injects the current activity here
#Inject Context context;
public void onCreate(#Observes OnCreateEvent e) {
// Wires up the error handling
Thread.setDefaultUncaughtExceptionHandler(new GeneralError(context));
}
}
public class MySpecificActivity {
// required in every activity that needs error handling
#Inject GlobalErrorHandler errorHandler;
}

Categories

Resources