I'm currently working on an android app where we use fragment
, reusable piece of UI (user interface). Those fragment become activated when
their receive data to display and can transition between multiple states (e.g. waiting data
, data displayed
, waiting delete response
,
waiting create response
, etc.) depending on user's input and the controller's response.
So they have a messy internal implicit state machine :
void receiveNotes(List<Note> notesToDisplay) {
if (viewIsCreated) {
listView.displayData(notesToDisplay);
notesReceived = true;
notesDisplayed = ture;
} else {
notesReceived = true;
notesDisplayed = false;
}
}
// etc.
Those kind of state machines are error prone.
Suppose your class has the following interface :
interface NotesPresenter {
void receiveNotes(List<Note> toDisplay);
void onViewCreated(View v);
}
And suppose that you can display notes only when the view is created.
class NotesPresenterFragment {
NotesPresenter previousState;
NotesPresenter currentState;
View view;
void setState(NotesPresenter nextState) {
previousState = currentState;
currentState = nextState;
}
void receiveNotes(List<Note> toDisplay) {
currentState.receiveNotes(toDisplay);
}
void onViewCreated(View v) {
currentState.onViewCreated(v);
}
}
class WaitingForView implements NotesPresenter {
List<Note> cachedNotes;
void receiveNotes(List<Note> toDisplay) {
cachedNotes = toDisplay;
}
void onViewCreated(View v) {
view = v;
setState(new WaitingForNotes());
if (cachedNotes != null) {
currentState.receiveNotes(cachedNotes);
}
}
}
class WaitingForNotes implements NotesPresenter {
void receiveNotes(List<Note> toDisplay) {
view.getListView().setData(toDisplay);
//etc.
setState(new WaitingForUserInput());
}
void onViewCreated(View v) {
//error.
}
}
class WaitingForUserInput implements NotesPresenter {
void receiveNotes(List<Note> toDisplay) {
//error.
}
void onViewCreated(View v) {
//error.
}
//methods to process user input
}
Using inner class for each state is verbose, but it clearly separates the allowed actions depending on the current state and it's a lot less error prone than using enums or booleans coupled with swith of if statements. Here is a short story to better explain the philosophy behind this pattern:
Three programmers were asked to cross a field and go to the house at the other side.
The novice programmer looks at the short distance and says, "it's not far!. That will take me ten minutes"
The senior programmer looks at the field, thinks for a while and says "I should be able to get there in a day". The novice looks surprised.
The ninja programmer looks at the field and says. "Looks like ten minutes, but I think fifteen should be enough". The senior programmer sneers.
The novice programmer sets off, but within a few moments, explosive land mines go off, blasting huge holes. Taking him off course, and requiring him to double back and attempt the crossing many times. It takes him two days to reach the goal. Although he is shaking and injured when he arrives.
The senior programmer sets off on all fours. And carefully taps the ground searching for mines, proceeding only when it is safe. Slowly and meticulously he crosses the field over the course of the day. Only setting-off a couple of mines.
The ninja programmer sets off, and walks directly across the field. Purposefully and directly. He arrives at the other side in just ten minutes.
"How did you do it"?, the others ask. "How come the mines didn't get you?"
"Easy" he replies. "I didn't plant any mines in the first place".
- If you dislike creating a new object for each state, use a
private final
anonymous inner class. - You can have a default implementation for each method
- Depending on the nature of your interactions with the user, you can have a stack of states that you unwind.