ControlFlow
control-flow library is designed to orchestrate and control the sequence of tasks within Android applications. It harnesses Kotlin coroutines and the Flow API to facilitate smooth task execution and management.
Install / Use
/learn @CodeStarX/ControlFlowREADME
ControlFlow
ControlFlow is an Android library that facilitates task sequencing, rollback actions, and error handling. It systematically oversees task execution, offering structured error handling and facilitating rollback processes for efficient management.
Explore the implementation of the controlflow library through the code samples available in the ControlFlowDemo. repository.
Features
- Task Execution Sequencing: Define and manage a sequence of tasks.
- Subtasks Execution Sequencing: Define and manage a sequence of subtasks for each primary task.
- Rollback Mechanism: Implement rollback functionalities for tasks that need to revert changes made during their execution.
- Error Handling: Handle errors occurring during task execution and initiate rollback processes if required.
- Automated Data Forwarding: Each task's output data type is automatically forwarded as the input for the subsequent task by default.
Installation
To include ControlFlow in your Android project, add the following dependency to your app's build.gradle file:
implementation("io.github.codestarx:control-flow:1.0.0-alpha11")
repositories {
//..
//..
mavenCentral()
}
Task Execution with ControlFlow
- Task Management: Create instances of tasks inheriting from
Dispatcherand implementingTaskProcessor. - ControlFlow Class: Use the
ControlFlowclass to manage task sequences and their execution flow. - Start Execution: Begin executing tasks using
start()method.
Usage
Task Implementation
Inherit from Dispatcher: Create tasks by inheriting from the Dispatcher class and implementing the TaskProcessor properties. For example:
class MyTask : Dispatcher(), TaskProcessor {
override val info: TaskInfo
get() = TaskInfo().apply {
index = 0
name = MyTask::class.java.name
runIn = Dispatchers.IO
}
override suspend fun doProcess(param: Any?): Flow<TaskStatus> {
// Define the action here
return launchAwait(
action = {
// Perform the action
// ...
},
transformer = {
// The output generated by this function will serve as the input for the subsequent task
// Return TransformData(data= ...)
},
actionCondition = {
// Define conditions for continuation or breaking
// Return ConditionData(status = ... , throwable = ... )
}
)
}
}
Handling Rollback
Tasks implementing rollback functionality should also override methods from the RollbackTaskProcessor interface, specifying rollback actions.
Example of a task with rollback:
class MyRollbackTask : Dispatcher(), RollbackTaskProcessor {
override val info: TaskInfo
get() = TaskInfo().apply {
index = 0
name = MyRollbackTask::class.java.name
runIn = Dispatchers.IO
}
override val rollbackInfo: RollbackInfo
get() = RollbackInfo().apply {
index = 0
name = MyRollbackTask::class.java.name
runIn = Dispatchers.IO
}
override suspend fun doProcess(param: Any?): Flow<TaskStatus> {
// Define the action for the task
return launchAwait(
action = {
// Perform the action
// ...
},
actionCondition = {
// Define conditions for continuation or breaking
// Return ConditionData(status = ... , throwable = ... )
}
)
}
override suspend fun doRollbackProcess(): Flow<TaskStatus> {
// Define the rollback action here
return launchAwait(
action = {
// Perform the rollback action
// ...
},
actionCondition = {
// Define conditions for rollback continuation or breaking
// Return ConditionData(status = ... , throwable = ... )
}
)
}
}
Automated Data Forwarding
Each task's output data type is automatically forwarded as the input for the subsequent task by default.
If you require altering the output data type passed to the next task, utilize the transformer method for this purpose.
Example:
class Task : Dispatcher(), TaskProcessor {
override val info: TaskInfo
get() = TaskInfo().apply {
index = 0
name = Task::class.java.name
runIn = Dispatchers.IO
}
override suspend fun doProcess(param: Any?): Flow<TaskStatus> {
// Define the action here
return launchAwait(
action = {
// Perform the action
// ...
},
transformer = {
// The output generated by this function will serve as the input for the subsequent task
// Return TransformData(data= ...)
},
actionCondition = {
// Define conditions for continuation or breaking
// Return ConditionData(status = ... , throwable = ... )
}
)
}
}
Attributes Of Each Task
The attributes of each task are outlined using the TaskInfo class.
The index, name and runIn parameters define the task's specifications and execution thread. By utilizing index or name and the startFrom method within the ControlFlow, tasks can be rerun as needed.
Example:
class Task : Dispatcher(), TaskProcessor {
get() = TaskInfo().apply {
index = 0
name = Task::class.java.name
runIn = Dispatchers.IO
}
override suspend fun doProcess(param: Any?): Flow<TaskStatus> {
...
}
Activate The Retry Mechanism For Each Task
To activate the Retry mechanism for each task, set the count to define the number of retries in case of failure.
Additionally, assign specific causes, a list of errors, to trigger retries upon encountering these errors.
Adjust the delay value to determine the interval between each retry attempt.
Example:
class Task : Dispatcher(), TaskProcessor {
get() = TaskInfo().apply {
index = 0
name = Task::class.java.name
retry = RetryStrategy().apply {
count = 2
causes = setOf(TimeoutException::class,AnotherException::class,...)
delay = 1000L
}
runIn = Dispatchers.IO
}
override suspend fun doProcess(param: Any?): Flow<TaskStatus> {
...
}
Attributes Of Each Rollback Task
The attributes of each task are outlined using the RollbackInfo class.
The index, name and runIn parameters define the task's specifications and execution thread. By utilizing index or name and the startRollbackFrom method within the ControlFlow, tasks can be rerun as needed.
Example:
class Task : Dispatcher(), RollbackTaskProcessor {
override val info: TaskInfo
get() = TaskInfo().apply {
index = 0
name = Task::class.java.name
runIn = Dispatchers.IO
}
override val rollbackInfo: RollbackInfo
get() = RollbackInfo().apply {
index = 0
name = Task::class.java.name
runIn = Dispatchers.IO
}
override suspend fun doProcess(param: Any?): Flow<TaskStatus> {
...
}
override suspend fun doRollbackProcess(): Flow<TaskStatus> {
...
}
Activate The Retry Mechanism For Each Rollback Task
To activate the Retry mechanism for each rollback task, set the count to define the number of retries in case of failure.
Additionally, assign specific causes, a list of errors, to trigger retries upon encountering these errors.
Adjust the delay value to determine the interval between each retry attempt.
Example:
class Task : Dispatcher(), RollbackTaskProcessor {
override val info: TaskInfo
get() = ...
override val rollbackInfo: RollbackInfo
get() = RollbackInfo().apply {
index = 0
name = Task::class.java.name
retry = RetryStrategy().apply {
count = 2
causes = setOf(TimeoutException::class,AnotherException::class,...)
delay = 1000L
}
runIn = Dispatchers.IO
}
override suspend fun doProcess(param: Any?): Flow<TaskStatus> {
...
}
override suspend fun doRollbackProcess(): Flow<TaskStatus> {
...
}
ControlFlow Class
ControlFlow manages the execution sequence of tasks and potential rollback actions.
It orchestrates the execution, rollback, completion, and error handling of tasks and their rollbacks.
This class offers a structured way to manage a series of tasks and handles their execution flow and potential rollbacks.
Running Control Flow
Example usage:
// Create a ControlFlow instance
val controlFlow = ControlFlow(object : WorkFlowTracker {
// Implement work Flow callback methods
})
// Define your tasks
controlFlow.startWith(MyTask())
controlFlow.then(AnotherTask())
controlFlow.then(AnotherTask())
// Set up TaskStatusTracker if needed
controlFlow.useTaskStatusTracker(object : TaskStatusTracker {
// Implement callback methods
})
// Set up RollbackStatusTracker if needed
controlFlow.useRollbackStatusTracker(object : RollbackStatusTracker {
// Implement callback methods
})
// Start executing tasks
controlFlow.start()
Subtasks Execution
To incorporate subtasks for each task, you can define their implementation as outlined below:
Example usage:
// Create a ControlFlow instance
val controlFlow = ControlFlow(object : WorkFlowTracker {
// Implement work Flow callback methods
})
// Define your tasks
controlFlow.startWith(MyTask().apply{
// Define your subtasks
then(subtask= MySubtask(
