-
Notifications
You must be signed in to change notification settings - Fork 0
Creating multi operation resident component
Sometimes a resident component need to be able to execute multiple operations (but one at a time). For example the main activity of an app may have to execute both login and logout operations. For cases like this AbstractMultiOperationResidentComponent
is used.
AbstractSideEffectOperationResidentComponent
builds upon OperationResidentComponentImpl
which basicaly means that we will have the same 3 states IDLE, BUSY, ENDED and the same semantics and functionality as in described in the beggining of Creating side effect resident component.
ℹ️ You can browse the source at Github.
For base project we will use Simplest forge app branch side_effect
and we will build upon it.
Clone it with:
git clone [email protected]:ogrebgr/forge-android-examples-basic.git
Then switch to side_effect
branch:
git checkout side_effect
and import it in Android Studio.
If you don't want to type the code go to the project dir and you can obtain the end result with:
git checkout multi_operation
Our unit will reside in units/multi_operation/
.
Our unit will be able to execute 2 operations (one at a time).
First operation will produce an integer as a result. Second operation will produce a float as a result. Real applications will produce more complex objects but for the sake of simplicity we will use Integer and Float.
First you will need to define an enum that lists the operations that the resident will execute. In our case it will look like this:
public enum Operation {
FIRST_OPERATION,
SECOND_OPERATION
}
Our resident interface will extend MultiOperationResidentComponent
.
It will look like this:
public interface ResMultiOperation extends MultiOperationResidentComponent<Operation> {
void executeFirstOperation(); // (1)
void executeSecondOperation(); // (2)
OperationOutcome<Integer, Void> getFirstOperationOutcome(); // (3)
OperationOutcome<Float, Void> getSecondOperationOutcome(); // (4)
}
- (1) method for executing the first operation
- (2) method for executing the second operation
- (3) getting the outcome of the first operation
- (4) getting the outcome of the second operation
OperationOutcome
is a value class that encapsulates the outcome of an operation. We will see how to use it shortly.
Our resident will extend AbstractMultiOperationResidentComponent
:
public class ResMultiOperationImpl extends AbstractMultiOperationResidentComponent<Operation>
implements ResMultiOperation {
We will need two fields that will hold the operations outcomes:
private OperationOutcome<Integer, Void> mFirstOperationOutcome;
private OperationOutcome<Float, Void> mSecondOperationOutcome;
Then we will define the first operation:
@Override
public void executeFirstOperation() {
if (isIdle()) { // (1)
switchToBusyState(Operation.FIRST_OPERATION); // (2)
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000); // (3)
} catch (InterruptedException e) {
e.printStackTrace();
}
mFirstOperationOutcome = OperationOutcome.createSuccessOutcome(42); // (4)
switchToEndedStateSuccess(); // (5)
}
});
t.start();
}
}
- (1) we check if the resident is in IDLE state because we don't want two operations to run in parallel.
- (2) switching to BUSY state stating which operations is executing. Btw,
switchToBusyState()
checks if it is called in IDLE state and throws exception otherwise but we don't want to die in such a noob way so that is why we check for IDLE in (1) - (3) artificial delay in order to simulate doing work
- (4) creating the success outcome
- (5) switching to completed state with success flag on.
... and the method for getting the outcome of the first operation:
@Override
public OperationOutcome<Integer, Void> getFirstOperationOutcome() {
return mFirstOperationOutcome;
}
Methods for the second operation are similar so we omit them. If you want to see them please go to the source.
ℹ️ Full source can be found here
Our activity will extend OpActivity
:
public class ActMultiOperation extends OpActivity<ResMultiOperation> {
We will have two buttons for starting the two operations and two TextView
s to show the results.
The code for the two operations is almost identical so we will show only the code for the first one.
We initialize the first button to call resident's executeFirstOperation()
:
ViewUtils.initButton(view, R.id.btn_first, new DebouncedOnClickListener() {
@Override
public void onDebouncedClick(View v) {
getRes().executeFirstOperation();
}
});
Handling of the state looks like:
@Override
protected synchronized void handleState() {
switch (getRes().getOpState()) { // (1)
case IDLE:
MyDialogs.hideCommWaitDialog(getFragmentManager()); // (2)
showData(); // (3)
break;
case BUSY:
MyDialogs.showCommWaitDialog(getFragmentManager()); // (4)
break;
case ENDED:
MyDialogs.hideCommWaitDialog(getFragmentManager()); // (5)
switch (getRes().getCurrentOperation()) { // (6)
case FIRST_OPERATION:
handleFirstOperationOutcome(); // (7)
break;
case SECOND_OPERATION:
handleSecondOperationOutcome();
break;
}
getRes().ack(); // (8)
break;
}
}
- (1) We switch on the resident's state
- (2) hiding the dialog if still shown (that may happen after configuration change when the OS will automatically restore it)
- (3) we update the data on the screen. If we don't have this call here after configuration change TextViews will be empty. Method body is shown bellow
- (4) show that we are busy
- (5) operation has ended so we hide the dialog
- (6) switching on the current operation that was executed. That way you can proceed depending of which operation has just ended.
- (7) calling a method to handle the outcome of the first operation which was just ended. Method will be defined bellow
- (8) Acknowledging that the ENDED state is observed
private void showData() {
OperationOutcome<Integer, Void> out1 = getRes().getFirstOperationOutcome();
if (out1 != null) {
showFirstData(out1);
}
// ... second op code
}
Handling the first operation outcome:
private void handleFirstOperationOutcome() {
OperationOutcome<Integer, Void> out = getRes().getFirstOperationOutcome(); // (1)
if (out.isSuccessful()) { // (2)
showFirstData(out); // (3)
} else {
MyDialogs.showCommProblemDialog(getFragmentManager()); // (4)
}
}
- (1) getting the outcome from the resident
- (2) checking if the operation ENDED successfully
- (3) on success we show the integer value
- (4) on error we show error dialog
We also need to call handleState()
in onResume()
in order to update the interface after activity was paused or recreated (after configuration change).
@Override
protected void onResume() {
super.onResume();
handleState();
}
❗ Don't forget to set ActMultiOperation
as launcher activity in the AndroidManifest.xml
.
Run the app and test it. Switch the orientation and see how the state is preserved.