-
Notifications
You must be signed in to change notification settings - Fork 97
Introduction
Keep Android At Arm’s Length
— Kevin Schultz, Droidcon NYC '14
According to dangerous experiments, heavy calculations and long running tests the perfect distance to the Android Framework is approximately thirty inches.
Androids biggest hurdle is the Activity Lifecycle.
Besides of that and some systems APIs it's plain Java.
Handling the resumed and paused state is easy.
With onPause
ond onResume
it is possible to start/stop unnecessary work (such as GPS) when the user doesn't use the app.
But it gets a lot harder when your Activity gets completely destroyed because of an orientation change.
Or the Activity
was in background and got garbage collected.
The Activity has the same API for both cases.
The state can be saved in onSaveInstanceState(Bundle)
and recovered when the Activity gets recreated.
But it's only possible to save serialized data. Strings, Integers, Booleans, arrays.
The idea to use the same mechanism to restore the state after a (possible) UI change and returning to an previously opened app was a reasonable decision.
But is it good practice to serialize all data and deserialize everything a few milliseconds later with the new created Activity object?
It would be easier without the serialization.
- What about long running operations like unzipping a file? Should the zipping stop and restart because the user rotated the phone?
- Should a network request to order a pizza be cancelled because a user taps a notification and leaves the app?
No. This is not the expected behaviour. But it happens all the time on Android devices with gigabytes of ram. Old laptops with 1GB ram are able to do all those things (and more) in parallel without a problem. Changing the screen size of a window, unzipping a file while waiting for the response when booking a flight. Everything simultaneously. Nobody fears that the flight will not be booked because he started chatting after submitting the form.
Android is capable to do those things in parallel as well.
There is the IntentService
.
Holding a reference to an AsyncTask
from a singleton could be another option. And many more.
But here is the thing: All those APIs are hard and often limited. Because of that, the decision to lock the screen rotation and stop all work when the app goes to background is often the cheapest and quickest solution. The bosses are happy if their app doesn't crash.
What is an Activity
?
An Activity
is the Object that gets started by the system when user taps a launcher icon.
The Activity allows us to add Views to a Window
which will be visible to the user.
There is no other API that can be used to show something to the user.
The Activity also provides a lot of callbacks when and how the UI is visible.
There are other methods to connect to system services but they aren't exclusively available using an Activity Object.
tl;dr An Activity
is the UI of an app and we can't get rid of it.
The idea behind this project is that the Activity
is only the UI (View
) and displays data but doesn't store them.
The data/state will be stored in an Object that lives longer than the Activity
.
This is the Presenter
.
- The
Presenter
isn't a singleton - When the
Activity
gets finished thePresenter
dies, too - The
Presenter
survives orientation changes - The
Presenter
survives when theActivity
got killed in background
The only information the Presenter
knows about the View
is if the View
is in a state to display information or not.
This results in a very easy Lifecycle:
The `Activity` gets started
The `Presenter` will be created (`onCreate()`)
The `Activity` is visible
The `Activity` will be attached to the `Presenter` (`onWakeUp()`)
The `Activity` my be invisible
The `Activity` will be detached form the `Presenter` (`onSleep()`)
The `Activity` is visible again
The `Activity` will be attached to the `Presenter` (`onWakeUp()`)
The `Activity` my be destroyed.
The `Activity` will be detached form the `Presenter` (`onSleep()`)
Another `Activity` my be attached to the `Presenter` (`onWakeUp()`)
The currently attached `Activity` gets finished
The Presenter gets destroyed (`onSleep()` followed by `onDestroy()`)
Four simple lifecycle methods and an Object
which is able to hold references to long running operations even when the system destroyed the Activity
.
The Presenter
is written in pure Java and so abstract that it has no idea if the View
is an AndroidView or a TerminalApplicationView.
The communication between View
and Presenter
is handled with an interface which should be so generalized that it could be implemented for both.
It's not forbidden but the Presenter
should NOT have a reference to a Context
. This would break the concept of "Keep Android At Arm’s Length".
MVP has a Model
a View
and a Presenter
.
This project doesn't provide a implementation of the Model
.
The heart of this project is the Presenter
.
You are free to use whatever you like. A database, a pojo, a service; connecting them to the Presenter is no magic and doesn't require an API.
Using injection is recommended.
First create an interface for your View
extending the empty ThirtyInch TiView
interface.
public interface HelloWorldView extends TiView {
void showText(final String text);
}
Add a Presenter
to your Activity
by extending from TiActivity<TiPresenter, TiView>
.
Another, advanced option is to use the plugin.
Also implement the TiView
interface.
The Activity is the view implementation.
Two methods have to be implemented:
providePresenter()
has to return an instance of the TiPresenter
.
This method will be called only once in onCreate(Bundle)
, the first time the Activity gets launched (savedInstanceState == null
).
Sadly there is no other way to create the Presenter
than creating it after the Activity
was launched.
Remember, the Activity
is the entry point.
The Activity
(this
) itself is the View
.
It will be bound to the presenter whenever the Activity
is visible to the User (onStart()
).
In case the TiActivity
doesn't implement the TiView
interface check TiActivity#provideView()
.
public class HelloWorldActivity extends TiActivity<HelloWorldPresenter, HelloWorldView>
implements HelloWorldView {
private TextView mOutput;
@Override
public void showText(final String text) {
mOutput.setText(text);
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hello_world);
mOutput = (TextView) findViewById(R.id.output);
}
@NonNull
@Override
public HelloWorldPresenter providePresenter() {
return new HelloWorldPresenter();
}
}
Creating the Presenter
and add your logic
public class HelloWorldPresenter extends TiPresenter<HelloWorldView> {
private String mText = "Hello World";
@Override
protected void onCreate() {
super.onCreate();
}
@Override
protected void onWakeUp() {
super.onWakeUp();
getView().showText(mText);
}
@Override
protected void onSleep() {
super.onSleep();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
Notice: mText
works here as "Model".