├── .gitignore
├── ControlFlow
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── gradle.properties
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── io
│ │ └── github
│ │ └── codestarx
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── io
│ │ └── github
│ │ └── codestarx
│ │ ├── ControlFlow.kt
│ │ ├── helper
│ │ ├── AnyExtensions.kt
│ │ ├── Dispatcher.kt
│ │ └── HttpException.kt
│ │ ├── interfaces
│ │ ├── RollbackStatusTracker.kt
│ │ ├── RollbackTaskProcessor.kt
│ │ ├── TaskProcessor.kt
│ │ ├── TaskStatusTracker.kt
│ │ └── WorkFlowTracker.kt
│ │ ├── models
│ │ ├── ConditionData.kt
│ │ ├── RetryStrategy.kt
│ │ ├── RollbackInfo.kt
│ │ ├── TaskFlow.kt
│ │ ├── TaskInfo.kt
│ │ └── TransformData.kt
│ │ └── status
│ │ ├── State.kt
│ │ └── TaskStatus.kt
│ └── test
│ └── java
│ └── io
│ └── github
│ └── codestarx
│ ├── ControlFlowTest.kt
│ ├── DispatcherTest.kt
│ └── core
│ └── BaseUnitTest.kt
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── io
│ │ └── github
│ │ └── codestarx
│ │ └── controlflowdevelop
│ │ └── MainActivity.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-mdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── values
│ ├── colors.xml
│ ├── strings.xml
│ └── themes.xml
│ └── xml
│ ├── backup_rules.xml
│ └── data_extraction_rules.xml
├── attach
└── attach.xml
├── build.gradle.kts
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows thumbnail db
2 | Thumbs.db
3 |
4 | # OSX files
5 | .DS_Store
6 |
7 | # built application files
8 | *.apk
9 | *.ap_
10 |
11 | # files for the dex VM
12 | *.dex
13 |
14 | # Java class files
15 | *.class
16 |
17 | # generated files
18 | bin/
19 | gen/
20 | build/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Eclipse project files
26 | .classpath
27 | .project
28 |
29 | # Android Studio
30 | .idea
31 | .gradle
32 | /*/local.properties
33 | /*/out
34 | /*/*/build
35 | /*/*/*/build
36 | build
37 | /*/*/production
38 | /*/*/*/production
39 | *.iml
40 | *.iws
41 | *.ipr
42 | *~
43 | *.swp
44 |
--------------------------------------------------------------------------------
/ControlFlow/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ControlFlow/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.vanniktech.maven.publish.SonatypeHost
2 |
3 | plugins {
4 | id("com.android.library")
5 | id("org.jetbrains.kotlin.android")
6 | id("com.vanniktech.maven.publish") version "0.26.0"
7 | }
8 |
9 | android {
10 | namespace = "io.github.codestarx"
11 | compileSdk = 34
12 |
13 | defaultConfig {
14 | minSdk = 16
15 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
16 | consumerProguardFiles("consumer-rules.pro")
17 | }
18 | buildTypes {
19 | release {
20 | isMinifyEnabled = false
21 | proguardFiles(
22 | getDefaultProguardFile("proguard-android-optimize.txt"),
23 | "proguard-rules.pro"
24 | )
25 | }
26 | }
27 | compileOptions {
28 | sourceCompatibility = JavaVersion.VERSION_17
29 | targetCompatibility = JavaVersion.VERSION_17
30 | }
31 | kotlinOptions {
32 | jvmTarget = "17"
33 | }
34 | }
35 |
36 | dependencies {
37 |
38 | val coroutine = "1.7.3"
39 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine")
40 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine")
41 |
42 | val lifecycle = "2.7.0-rc02"
43 | implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle")
44 | implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle")
45 |
46 | val hamcrest = "2.2"
47 | testImplementation("org.hamcrest:hamcrest-library:$hamcrest")
48 |
49 | val mockk = "1.12.1"
50 | testImplementation("io.mockk:mockk:$mockk")
51 |
52 | val testRules = "1.4.0"
53 | testImplementation("androidx.test:rules:$testRules")
54 |
55 | testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutine")
56 | testImplementation("junit:junit:4.13.2")
57 | androidTestImplementation("androidx.test.ext:junit:1.1.5")
58 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
59 | }
60 | mavenPublishing{
61 | coordinates("io.github.codestarx", "control-flow", "1.0.0-alpha11")
62 |
63 | publishToMavenCentral(SonatypeHost.S01)
64 | pom {
65 | name.set("control-flow")
66 | description.set("control-flow 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.")
67 | inceptionYear.set("2024")
68 | url.set("https://github.com/CodeStarX/ControlFlow/")
69 | packaging = "aar"
70 | licenses {
71 | license {
72 | name.set("The Apache License, Version 2.0")
73 | url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
74 | distribution.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
75 | }
76 | }
77 | developers {
78 | developer {
79 | id.set("xxx")
80 | name.set("Mohsen Soltanian")
81 | url.set("https://github.com/CodeStarX/")
82 | email.set("soltaniyan.mohsen@gmail.com")
83 | }
84 | }
85 | scm {
86 | url.set("https://github.com/CodeStarX/ControlFlow/")
87 | connection.set("scm:git:git://github.com/CodeStarX/ControlFlow.git")
88 | developerConnection.set("scm:git:ssh://git@github.com/CodeStarX/ControlFlow.git")
89 | }
90 | }
91 | }
--------------------------------------------------------------------------------
/ControlFlow/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeStarX/ControlFlow/d76e3fd6f00ed713dc5412eb0a07bae4749c174e/ControlFlow/consumer-rules.pro
--------------------------------------------------------------------------------
/ControlFlow/gradle.properties:
--------------------------------------------------------------------------------
1 | RELEASE_SIGNING_ENABLED=true
2 | signing.keyId=xxx
3 | signing.password=xxx
4 | signing.secretKeyRingFile=xxx
--------------------------------------------------------------------------------
/ControlFlow/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/ControlFlow/src/androidTest/java/io/github/codestarx/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("io.github.controlflow.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/ControlFlow/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/ControlFlow.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx
2 |
3 | import android.util.Log
4 | import androidx.annotation.Keep
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import io.github.codestarx.interfaces.WorkFlowTracker
8 | import io.github.codestarx.interfaces.TaskProcessor
9 | import io.github.codestarx.interfaces.RollbackTaskProcessor
10 | import io.github.codestarx.interfaces.RollbackStatusTracker
11 | import io.github.codestarx.interfaces.TaskStatusTracker
12 | import io.github.codestarx.models.TaskFlow
13 | import io.github.codestarx.status.State
14 | import io.github.codestarx.status.TaskStatus
15 | import kotlinx.coroutines.CoroutineExceptionHandler
16 | import kotlinx.coroutines.CoroutineScope
17 | import kotlinx.coroutines.Job
18 | import kotlinx.coroutines.delay
19 | import kotlinx.coroutines.flow.catch
20 | import kotlinx.coroutines.flow.onStart
21 | import kotlinx.coroutines.flow.retryWhen
22 | import kotlinx.coroutines.isActive
23 | import kotlinx.coroutines.launch
24 | import kotlinx.coroutines.withContext
25 | import java.util.concurrent.atomic.AtomicReference
26 |
27 | /**
28 | * ControlFlow manages the execution sequence of tasks and potential rollback actions.
29 | * It orchestrates the execution, rollback, completion, and error handling of tasks and their rollbacks.
30 | * This class offers a structured way to manage a series of tasks and handles their execution flow and potential rollbacks.
31 | * Example usage:
32 | *
33 | * // Create a ControlFlow instance
34 | * val controlFlow = ControlFlow(object : WorkFlowTracker {
35 | * // Implement work Flow callback methods
36 | * })
37 | *
38 | * // Define task sequence
39 | * controlFlow.startWith(Task1())
40 | * controlFlow.then(Task2())
41 | *
42 | * // Set up callbacks for task and rollback task execution progress
43 | * controlFlow.useTaskStatusTracker(object : TaskStatusTracker {
44 | * // Implement callback methods
45 | * })
46 | * controlFlow.useRollbackStatusTracker(object : RollbackStatusTracker {
47 | * // Implement rollback callback methods
48 | * })
49 | *
50 | * // Start executing tasks
51 | * controlFlow.start()
52 | *
53 | * // If an error occurs during execution, initiate rollback manually
54 | * controlFlow.startRollback()
55 | *
56 | * // Restart execution from a specific task or task index
57 | * controlFlow.startFrom(taskName = "Task2")
58 | * controlFlow.startFrom(taskIndex = 1)
59 | *
60 | * // Restart the entire sequence
61 | * controlFlow.restart()
62 | */
63 | @Keep
64 | class ControlFlow(
65 | private var workFlowTracker: WorkFlowTracker?
66 | ): ViewModel() {
67 |
68 | private val handler = CoroutineExceptionHandler { _, exception ->
69 | // Handles exceptions that occur within coroutines
70 | exception.message?.let { Log.e("ControlFlow", it) }
71 | }
72 |
73 | // Manages the execution of tasks within a coroutine scope
74 | private fun safeLauncher(block: suspend CoroutineScope.() -> Unit): Job {
75 | return viewModelScope.launch(handler, block = block)
76 | }
77 |
78 | // Stores the original sequence of tasks
79 | private var originalTasks: MutableList? = mutableListOf()
80 |
81 |
82 | // Stores the original sequence of rollback tasks
83 | private var originalRollbackTasks: MutableList? = mutableListOf()
84 |
85 | // Stores the tasks to be executed
86 | private var tasks: MutableList? = mutableListOf()
87 |
88 | // Stores the rollback tasks to be executed
89 | private var rollbackTasks: MutableList? = mutableListOf()
90 |
91 | // Stores the completed tasks during execution
92 | private var _completedTasks: MutableList? = mutableListOf()
93 |
94 | // Provides access to the list of completed tasks
95 | val completedTasks: List
96 | get() { return _completedTasks?.toList() ?: listOf() }
97 |
98 | // Stores the completed rollback tasks during execution
99 | private var _completedRollbackTasks: MutableList? = mutableListOf()
100 |
101 | // Provides access to the list of completed rollback tasks
102 | val completedRollbackTasks: List
103 | get() { return _completedRollbackTasks?.toList() ?: listOf() }
104 |
105 | // Tracks if the rollback should be automatically initiated
106 | private var runAutomaticallyRollback: AtomicReference? = null
107 |
108 | // Callback for the main task execution progress
109 | private var taskStatusTracker: TaskStatusTracker? = null
110 |
111 | // Callback for the rollback task execution progress
112 | private var rollbackStatusTracker: RollbackStatusTracker? = null
113 |
114 | // Coroutine job for executing tasks
115 | private var taskJob: Job? = null
116 |
117 | // Coroutine job for executing rollback tasks
118 | private var rollbackTaskJob: Job? = null
119 |
120 | private var taskResult: Any? = null
121 |
122 | private var taskIsCurrentlyInProgress: Boolean? = null
123 |
124 | /**
125 | * Add the first task to the control flow sequence.
126 | * @param first task to be added to the control flow sequence.
127 | */
128 | fun startWith(first: TaskProcessor) {
129 | originalTasks?.clear()
130 | tasks?.clear()
131 | originalTasks?.add(first)
132 | tasks?.add(first)
133 | if(TaskProcessor.subtasks?.containsKey(first.hashCode()) == true){
134 | originalTasks?.addAll(TaskProcessor.subtasks!![first.hashCode()] ?: mutableListOf())
135 | tasks?.addAll(TaskProcessor.subtasks!![first.hashCode()] ?: mutableListOf())
136 | }
137 | }
138 |
139 | /**
140 | * Adds the next task to the control flow sequence.
141 | * @param next The subsequent task to be added to the control flow sequence.
142 | */
143 | fun then(next: TaskProcessor) {
144 | originalTasks?.add(next)
145 | tasks?.add(next)
146 | if(TaskProcessor.subtasks?.containsKey(next.hashCode()) == true){
147 | originalTasks?.addAll(TaskProcessor.subtasks!![next.hashCode()] ?: mutableListOf())
148 | tasks?.addAll(TaskProcessor.subtasks!![next.hashCode()] ?: mutableListOf())
149 | }
150 | }
151 |
152 | /**
153 | * Starts executing the tasks in the control flow sequence.
154 | * @param runAutomaticallyRollback Set to true if you want tasks to automatically rollback on failure.
155 | */
156 | fun start(runAutomaticallyRollback: Boolean = false) {
157 | workFlowTracker?.started(this@ControlFlow)
158 | this.runAutomaticallyRollback?.set(runAutomaticallyRollback)
159 | runTasks()
160 | }
161 |
162 | private fun runTasks() {
163 | taskJob = safeLauncher {
164 | while (isActive) {
165 | when(tasks?.isNotEmpty()) {
166 | true -> {
167 | tasks?.first()?.let { executeTask(task = it) }
168 | }
169 | else -> {
170 | taskJob?.cancel()
171 | }
172 | }
173 | }
174 | }
175 | }
176 |
177 | /**
178 | * Starts executing the rollback tasks in the control flow sequence.
179 | */
180 |
181 | fun startRollback() {
182 | rollbackTaskJob = safeLauncher {
183 | while (isActive) {
184 | if(rollbackTasks?.isNotEmpty() == true){
185 | rollbackTasks?.last()?.let { executeRollback(task = it) }
186 | }else {
187 | rollbackTaskJob?.cancel()
188 | }
189 | }
190 | }
191 | }
192 |
193 | /**
194 | * Restarts the control flow from the beginning.
195 | */
196 | fun restart() {
197 | reset()
198 | runAutomaticallyRollback?.get()?.let { start(it) }
199 | }
200 |
201 | /**
202 | * Restarts the rollback process.
203 | */
204 | fun restartRollback() {
205 | resetRollback()
206 | startRollback()
207 | }
208 |
209 | /**
210 | * Starts executing tasks from a specific task name in the sequence.
211 | * @param taskName The name of the task from which to start the execution.
212 | */
213 | fun startFrom(taskName: String) {
214 | val index = originalTasks?.indexOfFirst { it.info.name == taskName }
215 | if (index == -1) {
216 | // Task not found, throw an exception or handle it accordingly
217 | return
218 | }
219 | setNewTask(index= index!!)
220 | runAutomaticallyRollback?.let { it.get()?.let { it1 -> start(it1) } }
221 | }
222 |
223 | /**
224 | * Starts executing tasks from a specific task index in the sequence.
225 | * @param taskIndex The index of the task from which to start the execution.
226 | */
227 | fun startFrom(taskIndex: Int) {
228 | val index = originalTasks?.indexOfFirst { it.info.index == taskIndex }
229 | if (index == -1) {
230 | // Task not found, throw an exception or handle it accordingly
231 | return
232 | }
233 | setNewTask(index= index!!)
234 | runAutomaticallyRollback?.let { it.get()?.let { it1 -> start(it1) } }
235 | }
236 |
237 | private fun setNewTask(index: Int) {
238 | val tasksToExecute = originalTasks?.size?.let { originalTasks?.subList(index, it) }
239 | tasks?.clear() // Clear existing tasks
240 | tasksToExecute?.let { tasks?.addAll(it) } // Set tasks to execute from the specified task onwards
241 | }
242 |
243 | /**
244 | * Initiates the rollback process from a specific task name in the rollback sequence.
245 | * @param taskName The name of the task from which to start the rollback process.
246 | */
247 | fun startRollbackFrom(taskName: String) {
248 | val index = originalRollbackTasks?.indexOfFirst { it.info.name == taskName }
249 | if (index == -1) {
250 | // Task not found, throw an exception or handle it accordingly
251 | return
252 | }
253 | setNewRollbackTask(index= index!!)
254 | startRollback()
255 | }
256 |
257 | /**
258 | * Initiates the rollback process from a specific task index in the rollback sequence.
259 | * @param taskIndex The index of the task from which to start the rollback process.
260 | */
261 | fun startRollbackFrom(taskIndex: Int) {
262 | val index = originalRollbackTasks?.indexOfFirst { it.info.index == taskIndex }
263 | if (index == -1) {
264 | // Task not found, throw an exception or handle it accordingly
265 | return
266 | }
267 | setNewRollbackTask(index= index!!)
268 | startRollback()
269 | }
270 |
271 | private fun setNewRollbackTask(index: Int){
272 | val tasksToExecute =
273 | originalRollbackTasks?.size?.let { originalRollbackTasks?.subList(index, it) }
274 | rollbackTasks?.clear() // Clear existing tasks
275 | tasksToExecute?.let { rollbackTasks?.addAll(it) } // Set tasks to execute from the specified task onwards
276 |
277 | }
278 |
279 | /**
280 | * Associates a callback for the main task execution.
281 | * @param callBack The callback to be associated with the main task execution.
282 | */
283 | fun useTaskStatusTracker(callBack: TaskStatusTracker){
284 | this.taskStatusTracker = callBack
285 | }
286 |
287 | /**
288 | * Associates a callback for the rollback task execution.
289 | * @param callBack The callback to be associated with the rollback task execution.
290 | */
291 | fun useRollbackStatusTracker( callBack: RollbackStatusTracker){
292 | this.rollbackStatusTracker = callBack
293 | }
294 |
295 | private suspend fun executeTask(task: TaskProcessor) {
296 | val taskFlow = withContext(task.info.runIn) { task.doProcess(param = taskResult) }
297 | taskFlow
298 | .onStart {
299 | if(taskIsCurrentlyInProgress == null){
300 | workFlowTracker?.taskStatus(controlFlow = this@ControlFlow, taskFlow = TaskFlow().apply {
301 | taskIndex = task.info.index
302 | taskName = task.info.name },state= State.Started)
303 | delay(10L)
304 | workFlowTracker?.taskStatus(controlFlow = this@ControlFlow,taskFlow = TaskFlow().apply {
305 | taskIndex = task.info.index
306 | taskName = task.info.name },state= State.InProgress)
307 | }
308 | }
309 | .retryWhen { cause, attempt ->
310 | val isUseRetryStrategy = task.info.retry?.count != null &&
311 | task.info.retry?.count!! > 0 &&
312 | !task.info.retry?.causes.isNullOrEmpty() &&
313 | task.info.retry?.delay != null && task.info.retry?.delay!! > 0L
314 |
315 | when(isUseRetryStrategy) {
316 | true -> {
317 | if(task.info.retry?.count!! >= attempt+1 && task.info.retry?.causes?.contains(cause::class) == true){
318 | taskIsCurrentlyInProgress = true
319 | delay(task.info.retry?.delay!!)
320 | return@retryWhen true
321 | }else {
322 | return@retryWhen false
323 | }
324 | }
325 | false -> {
326 | return@retryWhen false
327 | }
328 | }
329 |
330 | }
331 | .catch {
332 | tasks?.remove(element = task)
333 | taskStatusTracker?.failure(controlFlow = this@ControlFlow, info = task.info, errorCause = it)
334 | if(runAutomaticallyRollback?.get() == true) {
335 | if (rollbackTasks?.isNotEmpty() == true) {
336 | startRollback()
337 | }else{
338 | workFlowTracker?.completed(controlFlow = this@ControlFlow)
339 | }
340 | } else{
341 | workFlowTracker?.completed(controlFlow = this@ControlFlow)
342 | }
343 | taskIsCurrentlyInProgress = null
344 | taskJob?.cancel()
345 | }
346 | .collect { taskStatus ->
347 | handleTaskStatus(task, taskStatus)
348 | delay(10L)
349 | }
350 | }
351 |
352 | private suspend fun executeRollback(task: RollbackTaskProcessor) {
353 | val rollbackFlow = withContext(task.rollbackInfo.runIn) { task.doRollbackProcess() }
354 | rollbackFlow
355 | .onStart {
356 | if(taskIsCurrentlyInProgress == null){
357 | workFlowTracker?.taskStatus(controlFlow = this@ControlFlow,taskFlow = TaskFlow().apply {
358 | taskIndex = task.rollbackInfo.index
359 | taskName = task.rollbackInfo.name
360 | isRollback = true
361 | }, state = State.Started)
362 | delay(10L)
363 | workFlowTracker?.taskStatus(controlFlow = this@ControlFlow,taskFlow = TaskFlow().apply {
364 | taskIndex = task.rollbackInfo.index
365 | taskName = task.rollbackInfo.name
366 | isRollback = true
367 | }, state = State.InProgress)
368 | }
369 | }
370 | .retryWhen { cause, attempt ->
371 | val isUseRetryStrategy = task.rollbackInfo.retry?.count != null &&
372 | task.rollbackInfo.retry?.count!! > 0 &&
373 | !task.rollbackInfo.retry?.causes.isNullOrEmpty() &&
374 | task.rollbackInfo.retry?.delay != null && task.rollbackInfo.retry?.delay!! > 0L
375 |
376 | when(isUseRetryStrategy) {
377 | true -> {
378 | if(task.rollbackInfo.retry?.count!! >= attempt+1 && task.rollbackInfo.retry?.causes?.contains(cause::class) == true){
379 | taskIsCurrentlyInProgress = true
380 | delay(task.rollbackInfo.retry?.delay!!)
381 | return@retryWhen true
382 | }else {
383 | return@retryWhen false
384 | }
385 | }
386 | false -> {
387 | return@retryWhen false
388 | }
389 | }
390 |
391 | }
392 | .catch {
393 | rollbackTasks?.remove(element = task)
394 | rollbackStatusTracker?.failure(controlFlow = this@ControlFlow, info = task.rollbackInfo,errorCause= it)
395 | workFlowTracker?.completed(controlFlow = this@ControlFlow)
396 | rollbackTaskJob?.cancel()
397 | taskIsCurrentlyInProgress = null
398 | }
399 | .collect { rollbackStatus ->
400 | handleRollbackStatus(task, rollbackStatus)
401 | delay(10L)
402 | }
403 | }
404 |
405 | private fun handleTaskStatus(task: TaskProcessor, taskStatus: TaskStatus) {
406 | when (taskStatus) {
407 | is TaskStatus.DoneSuccessfully<*> -> {
408 | tasks?.remove(element = task)
409 | _completedTasks?.add(task)
410 | taskResult = taskStatus.result
411 | taskStatusTracker?.successful(controlFlow = this@ControlFlow, info = task.info, result = taskStatus.result)
412 | if(task is RollbackTaskProcessor) {
413 | originalRollbackTasks?.add(task)
414 | rollbackTasks?.add(task)
415 | }
416 | if(tasks?.isEmpty() == true) {
417 | workFlowTracker?.completed(controlFlow = this@ControlFlow)
418 | taskJob?.cancel()
419 | }
420 | }
421 | }
422 | }
423 |
424 | private fun handleRollbackStatus(task: RollbackTaskProcessor, rollbackStatus: TaskStatus) {
425 | when (rollbackStatus) {
426 | is TaskStatus.DoneSuccessfully<*> -> {
427 | rollbackTasks?.remove(element = task)
428 | _completedRollbackTasks?.add(task)
429 | rollbackStatusTracker?.successful(controlFlow= this@ControlFlow, info = task.rollbackInfo, result = rollbackStatus.result)
430 | if(rollbackTasks?.isEmpty() == true) {
431 | workFlowTracker?.completed(controlFlow = this@ControlFlow)
432 | rollbackTaskJob?.cancel()
433 | }
434 | }
435 | }
436 |
437 | }
438 |
439 | private fun reset(){
440 | originalRollbackTasks?.clear()
441 | tasks?.clear()
442 | _completedTasks?.clear()
443 | originalTasks?.let { tasks?.addAll(it) }
444 | taskIsCurrentlyInProgress = null
445 |
446 | }
447 |
448 | private fun resetRollback() {
449 | rollbackTasks?.clear()
450 | rollbackTasks = originalRollbackTasks
451 | _completedRollbackTasks?.clear()
452 | taskIsCurrentlyInProgress = null
453 | }
454 |
455 | /**
456 | * Stops and cleans up the resources associated with the task processing system.
457 | * This method sets various internal variables to null, effectively releasing
458 | * the references to the task-related objects, job instances, and other components.
459 | */
460 | fun stop() {
461 | originalTasks = null
462 | tasks = null
463 | originalRollbackTasks = null
464 | rollbackTasks = null
465 | _completedTasks = null
466 | _completedRollbackTasks = null
467 | runAutomaticallyRollback = null
468 | workFlowTracker = null
469 | taskStatusTracker = null
470 | rollbackStatusTracker = null
471 | taskJob = null
472 | rollbackTaskJob = null
473 | taskResult = null
474 | taskIsCurrentlyInProgress = null
475 | TaskProcessor.subtasks = null
476 | }
477 | }
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/helper/AnyExtensions.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.helper
2 |
3 | import androidx.annotation.Keep
4 |
5 | @Keep
6 | fun Boolean.Companion.successMode() = true
7 |
8 | @Keep
9 | fun Boolean.Companion.failureMode() = false
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/helper/Dispatcher.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.helper
2 |
3 | import android.util.Log
4 | import androidx.annotation.Keep
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.viewModelScope
7 | import io.github.codestarx.models.ConditionData
8 | import io.github.codestarx.models.TransformData
9 | import io.github.codestarx.status.TaskStatus
10 | import kotlinx.coroutines.CoroutineExceptionHandler
11 | import kotlinx.coroutines.CoroutineScope
12 | import kotlinx.coroutines.flow.Flow
13 | import kotlinx.coroutines.flow.flow
14 | import kotlinx.coroutines.launch
15 | import kotlin.coroutines.resume
16 | import kotlin.coroutines.resumeWithException
17 | import kotlin.coroutines.suspendCoroutine
18 |
19 | typealias Action = (onContinuation: (Any) -> Unit, onFailure: (Throwable) -> Unit) -> Unit
20 | typealias ActionUnit = (onContinuation: () -> Unit,onFailure: (Throwable) -> Unit) -> Unit
21 | const val THROWABLE_DEFAULT_MESSAGE = "You haven't utilized Throwable for This Task. By default, this message is set, but for optimal performance, it's advisable to specifically define the throwable value in relation to the Task."
22 | @Keep
23 | abstract class Dispatcher: ViewModel() {
24 |
25 | private val handler = CoroutineExceptionHandler { _, exception ->
26 | exception.message?.let { Log.e("Dispatcher", it) }
27 | }
28 |
29 | /**
30 | * Safely launches a coroutine with error handling using a provided block.
31 | * Usage:
32 | * Call this method with a suspend function to execute as a coroutine.
33 | * Example:
34 | * ```
35 | * safeLauncher { mySuspendFunction() }
36 | * ```
37 | */
38 | fun safeLauncher(block: suspend CoroutineScope.() -> Unit) {
39 | viewModelScope.launch(handler, block = block)
40 | }
41 |
42 |
43 | /**
44 | * Executes an action and emits TaskStatus based on the result or error.
45 | * No transformation applied to the result.
46 | * Usage:
47 | * Call this method with an action and handle its continuation and failure scenarios.
48 | * Example:
49 | * ```
50 | * launchAwait(action = { onContinue, onFailure -> myAction(onContinue, onFailure) })
51 | * ```
52 | */
53 | fun launchAwait(
54 | action: Action
55 | ): Flow = flow {
56 | try {
57 | val result = suspendCoroutine { continuation ->
58 | action.invoke({
59 | continuation.resume(value = TaskStatus.DoneSuccessfully(result = it))
60 | },{
61 | continuation.resumeWithException(it)
62 | })
63 | }
64 | emit(value = result)
65 | }catch (throwable: Throwable){
66 | throw throwable
67 | }
68 | }
69 |
70 |
71 | /**
72 | * Executes an action and emits TaskStatus based on conditions applied to the result.
73 | * No transformation applied to the result.
74 | * Usage:
75 | * Call this method with an action and conditions to handle different results.
76 | * Example:
77 | * ```
78 | * launchAwait(
79 | * action = { onContinue, onFailure -> myAction(onContinue, onFailure) },
80 | * actionCondition = { result -> checkConditions(result) }
81 | * )
82 | * ```
83 | */
84 | @JvmName("launchAwaitCondition")
85 | fun launchAwait(
86 | action: Action,
87 | actionCondition: (Any) -> ConditionData
88 | ): Flow = flow {
89 | try {
90 | val result = suspendCoroutine { continuation ->
91 | action.invoke({
92 | val result = actionCondition.invoke(it)
93 | when(result.status) {
94 | true -> {
95 | continuation.resume(value = TaskStatus.DoneSuccessfully(result = it))
96 | }
97 | else -> {
98 | continuation.resumeWithException(result.throwable ?: Throwable(THROWABLE_DEFAULT_MESSAGE)
99 | )
100 | }
101 | }
102 | },{
103 | continuation.resumeWithException(it)
104 | })
105 | }
106 | emit(value = result)
107 | }catch (throwable: Throwable){
108 | throw throwable
109 | }
110 | }
111 |
112 | /**
113 | * Executes an action and emits TaskStatus based on conditions applied to the result.
114 | * Applies transformation to the result before emitting the status.
115 | * Usage:
116 | * Call this method with an action, a transformation, and conditions to handle different results.
117 | * Example:
118 | * ```
119 | * launchAwait(
120 | * action = { onContinue, onFailure -> myAction(onContinue, onFailure) },
121 | * transformer = { result -> transformResult(result) },
122 | * actionCondition = { result -> checkConditions(result) }
123 | * )
124 | * ```
125 | */
126 | fun launchAwait(
127 | action: Action,
128 | actionCondition: (Any) -> ConditionData,
129 | transformer: (Any) -> TransformData
130 | ): Flow = flow {
131 | try {
132 | val result = suspendCoroutine { continuation ->
133 | action.invoke({
134 | val result = actionCondition.invoke(it)
135 | when(result.status) {
136 | true -> {
137 | continuation.resume(value = TaskStatus.DoneSuccessfully(result = transformer.invoke(it).data))
138 | }
139 | else -> {
140 | continuation.resumeWithException(result.throwable ?: Throwable(THROWABLE_DEFAULT_MESSAGE)
141 | )
142 | }
143 | }
144 | },{
145 | continuation.resumeWithException(it)
146 | })
147 | }
148 | emit(value = result)
149 | }catch (throwable: Throwable){
150 | throw throwable
151 | }
152 | }
153 |
154 | /**
155 | * Executes an action and emits TaskStatus based on the transformed result.
156 | * Applies transformation to the result before emitting the status.
157 | * Usage:
158 | * Call this method with an action and apply a transformation to emit the status.
159 | * Example:
160 | * ```
161 | * launchAwait(
162 | * action = { onContinue, onFailure -> myAction(onContinue, onFailure) },
163 | * transformer = { result -> transformResult(result) }
164 | * )
165 | * ```
166 | */
167 | @JvmName("launchAwaitTransformer")
168 | fun launchAwait(
169 | action: Action,
170 | transformer: (Any) -> TransformData
171 | ): Flow = flow {
172 | try {
173 | val result = suspendCoroutine { continuation ->
174 | action.invoke({
175 | continuation.resume(value = TaskStatus.DoneSuccessfully(result = transformer.invoke(it).data))
176 | },{
177 | continuation.resumeWithException(it)
178 | })
179 | }
180 | emit(value = result)
181 | }catch (throwable: Throwable){
182 | throw throwable
183 | }
184 | }
185 |
186 | /**
187 | * Executes an action without a result and emits TaskStatus.
188 | * Usage:
189 | * Call this method with an action that doesn't return a result and handle failure scenarios.
190 | * Example:
191 | * ```
192 | * launchUnitAwait(
193 | * action = { onContinue, onFailure -> myAction(onContinue, onFailure) }
194 | * )
195 | * ```
196 | */
197 | fun launchUnitAwait(
198 | action: ActionUnit
199 | ): Flow = flow {
200 | try {
201 | val result = suspendCoroutine { continuation ->
202 | action.invoke({
203 | continuation.resume(value = TaskStatus.DoneSuccessfully(result = Unit))
204 | },{
205 | continuation.resumeWithException(it)
206 | })
207 | }
208 | emit(value = result)
209 | }catch (throwable: Throwable){
210 | throw throwable
211 | }
212 | }
213 |
214 | /**
215 | * Executes a Flow and emits TaskStatus based on conditions applied to the collected result.
216 | * No transformation applied to the result.
217 | * Usage:
218 | * Call this method with a suspend function returning a Flow and handle result conditions.
219 | * Example:
220 | * ```
221 | * launchFlow(
222 | * action = { myFlowFunction() },
223 | * actionCondition = { result -> checkConditions(result) }
224 | * )
225 | * ```
226 | */
227 | @JvmName("launchFlowCondition")
228 | fun launchFlow(
229 | action: suspend () -> Flow,
230 | actionCondition: (T) -> ConditionData
231 | ): Flow = flow {
232 | var result: T? = null
233 | try {
234 | action.invoke().collect { value ->
235 | result = value
236 | }
237 | }catch (throwable: Throwable) {
238 | throw throwable
239 | }
240 | finally {
241 | if(result != null){
242 | val conResult = actionCondition.invoke(result!!)
243 | when(conResult.status){
244 | true -> {
245 | emit(value = TaskStatus.DoneSuccessfully(result = result))
246 | }
247 | else -> {
248 | throw conResult.throwable ?: Throwable(THROWABLE_DEFAULT_MESSAGE)
249 | }
250 | }
251 | }
252 |
253 | }
254 | }
255 |
256 |
257 |
258 | /**
259 | * Executes a Flow and emits TaskStatus based on conditions applied to the transformed result.
260 | * Applies transformation to the collected result before emitting the status.
261 | * Usage:
262 | * Call this method with a suspend function returning a Flow, a transformation, and conditions.
263 | * Example:
264 | * ```
265 | * launchFlow(
266 | * action = { myFlowFunction() },
267 | * transformer = { result -> transformResult(result) },
268 | * actionCondition = { result -> checkConditions(result) }
269 | * )
270 | * ```
271 | */
272 | fun launchFlow(
273 | action: suspend () -> Flow,
274 | actionCondition: (T) -> ConditionData,
275 | transformer: (T) -> TransformData,
276 | ): Flow = flow {
277 | var result: T? = null
278 | try {
279 | action.invoke().collect { value ->
280 | result = value
281 | }
282 | }catch (throwable: Throwable) {
283 | throw throwable
284 | }
285 | finally {
286 | if(result != null){
287 | val conResult = actionCondition.invoke(result!!)
288 | when(conResult.status){
289 | true -> {
290 | emit(value = TaskStatus.DoneSuccessfully(result = transformer.invoke(result!!).data))
291 | }
292 | else -> {
293 | throw conResult.throwable ?:
294 | Throwable(THROWABLE_DEFAULT_MESSAGE)
295 | }
296 | }
297 | }
298 |
299 | }
300 | }
301 |
302 | /**
303 | * Executes a Flow and emits TaskStatus based on the transformed result.
304 | * Applies transformation to the collected result before emitting the status.
305 | * Usage:
306 | * Call this method with a suspend function returning a Flow and apply a transformation.
307 | * Example:
308 | * ```
309 | * launchFlow(
310 | * action = { myFlowFunction() },
311 | * transformer = { result -> transformResult(result) }
312 | * )
313 | * ```
314 | */
315 | @JvmName("launchFlowTransformer")
316 | fun launchFlow(
317 | action: suspend () -> Flow,
318 | transformer: (T) -> TransformData
319 | ): Flow = flow {
320 | var result: T? = null
321 | try {
322 | action.invoke().collect { value ->
323 | result = value
324 | }
325 | }catch (throwable: Throwable) {
326 | throw throwable
327 | }
328 | finally {
329 | if(result != null){
330 | emit(value = TaskStatus.DoneSuccessfully(result = transformer.invoke(result!!).data))
331 | }
332 |
333 | }
334 | }
335 |
336 | /**
337 | * Executes a Flow and emits TaskStatus based on the collected result.
338 | * No conditions or transformations applied to the result.
339 | * Usage:
340 | * Call this method with a suspend function returning a Flow and emit the status based on the result.
341 | * Example:
342 | * ```
343 | * launchFlow(
344 | * action = { myFlowFunction() }
345 | * )
346 | * ```
347 | */
348 | fun launchFlow(
349 | action: suspend () -> Flow
350 | ): Flow = flow {
351 | var result: T? = null
352 | try {
353 | action.invoke().collect { value ->
354 | result = value
355 | }
356 | }catch (throwable: Throwable) {
357 | throw throwable
358 | }
359 | finally {
360 | if(result != null){
361 | emit(value = TaskStatus.DoneSuccessfully(result = result))
362 | }
363 | }
364 | }
365 |
366 |
367 | /**
368 | * Executes an action and emits TaskStatus based on conditions applied to the result.
369 | * Usage:
370 | * Call this method with an action and conditions to handle different results.
371 | * Example:
372 | * ```
373 | * launch(
374 | * action = { myAction() },
375 | * actionCondition = { result -> checkConditions(result) }
376 | * )
377 | * ```
378 | */
379 | @JvmName("launchCondition")
380 | fun launch(
381 | action: () -> T,
382 | actionCondition: (T) -> ConditionData
383 | ): Flow = flow {
384 | var result: T? = null
385 | try {
386 | result = action.invoke()
387 |
388 | }catch (throwable: Throwable) {
389 | throw throwable
390 | }
391 | finally {
392 | if(result != null){
393 | val conResult = actionCondition.invoke(result)
394 | when(conResult.status){
395 | true -> {
396 | emit(value = TaskStatus.DoneSuccessfully(result = result))
397 | }
398 | else -> {
399 | throw conResult.throwable ?: Throwable(THROWABLE_DEFAULT_MESSAGE)
400 | }
401 | }
402 | }
403 | }
404 | }
405 |
406 | /**
407 | * Executes an action and emits TaskStatus.
408 | * Usage:
409 | * Call this method with an action and emit the status based on the result or error.
410 | * Example:
411 | * ```
412 | * launch(
413 | * action = { myAction() }
414 | * )
415 | * ```
416 | */
417 | fun launch(
418 | action: () -> T
419 | ): Flow = flow {
420 | try {
421 | emit(value = TaskStatus.DoneSuccessfully(result = action.invoke()))
422 | }catch (throwable: Throwable) {
423 | throw throwable
424 | }
425 | }
426 |
427 | /**
428 | * Executes an action and emits TaskStatus based on the transformed result.
429 | * Applies transformation to the result before emitting the status.
430 | * Usage:
431 | * Call this method with an action and apply a transformation to emit the status.
432 | * Example:
433 | * ```
434 | * launch(
435 | * action = { myAction() },
436 | * transformer = { result -> transformResult(result) }
437 | * )
438 | * ```
439 | */
440 | @JvmName("launchTransformer")
441 | fun launch(
442 | action: () -> T,
443 | transformer: (T) -> TransformData
444 | ): Flow = flow {
445 | try {
446 | emit(value = TaskStatus.DoneSuccessfully(result = transformer.invoke(action.invoke()).data))
447 | }catch (throwable: Throwable) {
448 | throw throwable
449 | }
450 | }
451 |
452 | }
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/helper/HttpException.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.helper
2 |
3 | import androidx.annotation.Keep
4 |
5 | @Keep
6 | // Custom exception for HTTP errors
7 | class HttpException(val code: Int?, message: String?) : Throwable(message)
8 |
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/interfaces/RollbackStatusTracker.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.interfaces
2 |
3 | import androidx.annotation.Keep
4 | import io.github.codestarx.ControlFlow
5 | import io.github.codestarx.models.RollbackInfo
6 |
7 | @Keep
8 | interface RollbackStatusTracker {
9 | fun successful(controlFlow: ControlFlow, info: RollbackInfo, result: Any?)
10 | fun failure(controlFlow: ControlFlow, info: RollbackInfo, errorCause: Throwable?)
11 | }
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/interfaces/RollbackTaskProcessor.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.interfaces
2 |
3 | import androidx.annotation.Keep
4 | import io.github.codestarx.models.RollbackInfo
5 | import io.github.codestarx.status.TaskStatus
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | @Keep
9 | interface RollbackTaskProcessor : TaskProcessor {
10 | val rollbackInfo: RollbackInfo
11 | suspend fun doRollbackProcess(): Flow
12 |
13 | }
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/interfaces/TaskProcessor.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.interfaces
2 |
3 | import androidx.annotation.Keep
4 | import io.github.codestarx.status.TaskStatus
5 | import io.github.codestarx.models.TaskInfo
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | @Keep
9 | interface TaskProcessor {
10 |
11 | val info: TaskInfo
12 |
13 | suspend fun doProcess(param: Any?): Flow
14 |
15 | companion object {
16 | var subtasks: MutableMap?>? = mutableMapOf()
17 | }
18 |
19 | fun then(subtask: TaskProcessor) {
20 | val key = this@TaskProcessor.hashCode()
21 | if(subtasks?.containsKey(key) == false){
22 | subtasks!![key] = mutableListOf(subtask)
23 | }else {
24 | val values = subtasks?.getValue(key)
25 | values?.add(subtask)
26 | subtasks?.set(key, values)
27 | }
28 |
29 | }
30 | }
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/interfaces/TaskStatusTracker.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.interfaces
2 |
3 | import androidx.annotation.Keep
4 | import io.github.codestarx.ControlFlow
5 | import io.github.codestarx.models.TaskInfo
6 |
7 | @Keep
8 | interface TaskStatusTracker {
9 | fun successful(controlFlow: ControlFlow, info: TaskInfo, result: Any?)
10 | fun failure(controlFlow: ControlFlow, info: TaskInfo, errorCause: Throwable?)
11 |
12 | }
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/interfaces/WorkFlowTracker.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.interfaces
2 |
3 | import androidx.annotation.Keep
4 | import io.github.codestarx.ControlFlow
5 | import io.github.codestarx.models.TaskFlow
6 | import io.github.codestarx.status.State
7 |
8 | @Keep
9 | interface WorkFlowTracker {
10 | fun started(controlFlow: ControlFlow)
11 | fun taskStatus(controlFlow: ControlFlow, taskFlow: TaskFlow, state: State)
12 | fun completed(controlFlow: ControlFlow)
13 | }
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/models/ConditionData.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.models
2 |
3 | import androidx.annotation.Keep
4 |
5 | @Keep
6 | data class ConditionData(val status: Boolean?, val throwable: Throwable? = null)
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/models/RetryStrategy.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.models
2 |
3 | import androidx.annotation.Keep
4 | import kotlin.reflect.KClass
5 |
6 | @Keep
7 | class RetryStrategy {
8 | var count: Int? = null
9 | var causes: Set>? = null
10 | var delay: Long? = null
11 | }
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/models/RollbackInfo.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.models
2 |
3 | import androidx.annotation.Keep
4 |
5 | @Keep
6 | class RollbackInfo: TaskInfo()
7 |
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/models/TaskFlow.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.models
2 |
3 | import androidx.annotation.Keep
4 |
5 | @Keep
6 | class TaskFlow {
7 | var taskIndex: Int? = null
8 | var taskName: String? = null
9 | var isRollback: Boolean? = false
10 | }
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/models/TaskInfo.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.models
2 |
3 | import androidx.annotation.Keep
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlin.coroutines.CoroutineContext
6 | @Keep
7 | open class TaskInfo {
8 | var index: Int? = null
9 | var name: String? = null
10 | var retry: RetryStrategy? = null
11 | var runIn: CoroutineContext = Dispatchers.Default
12 | }
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/models/TransformData.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.models
2 |
3 | import androidx.annotation.Keep
4 |
5 | @Keep
6 | data class TransformData(val data: T? = null)
7 |
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/status/State.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.status
2 |
3 | import androidx.annotation.Keep
4 |
5 | @Keep
6 | enum class State {
7 | Started, InProgress
8 | }
--------------------------------------------------------------------------------
/ControlFlow/src/main/java/io/github/codestarx/status/TaskStatus.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.status
2 |
3 | import androidx.annotation.Keep
4 |
5 | @Keep
6 | sealed class TaskStatus {
7 | @Keep
8 | data class DoneSuccessfully(val result: T) : TaskStatus()
9 | }
10 |
--------------------------------------------------------------------------------
/ControlFlow/src/test/java/io/github/codestarx/ControlFlowTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx
2 |
3 |
4 | import io.github.codestarx.core.BaseUnitTest
5 | import io.github.codestarx.interfaces.RollbackTaskProcessor
6 | import io.github.codestarx.interfaces.TaskProcessor
7 | import io.github.codestarx.interfaces.WorkFlowTracker
8 | import io.github.codestarx.models.TaskInfo
9 | import io.github.codestarx.status.TaskStatus
10 | import io.mockk.coEvery
11 | import io.mockk.coVerifyOrder
12 | import io.mockk.every
13 | import io.mockk.impl.annotations.MockK
14 | import io.mockk.just
15 | import io.mockk.mockk
16 | import io.mockk.runs
17 | import kotlinx.coroutines.Dispatchers
18 | import kotlinx.coroutines.flow.flowOf
19 | import kotlinx.coroutines.test.TestCoroutineDispatcher
20 | import kotlinx.coroutines.test.resetMain
21 | import kotlinx.coroutines.test.runTest
22 | import kotlinx.coroutines.test.setMain
23 | import org.junit.Assert.assertEquals
24 | import org.junit.Test
25 |
26 | class ControlFlowTest: BaseUnitTest() {
27 |
28 | private lateinit var controlFlow: ControlFlow
29 |
30 | @MockK
31 | private lateinit var workFlowTracker: WorkFlowTracker
32 | @MockK
33 | private lateinit var taskProcessor: TaskProcessor
34 | @MockK
35 | private lateinit var rollbackTaskProcessor: RollbackTaskProcessor
36 |
37 |
38 | private val testDispatcher = TestCoroutineDispatcher()
39 |
40 | override fun onSetUpTest() {
41 | super.onSetUpTest()
42 | // Set the main dispatcher for testing
43 | Dispatchers.setMain(testDispatcher)
44 | controlFlow = ControlFlow(workFlowTracker)
45 | }
46 |
47 | @Test
48 | fun `test adding a task with startWith`() {
49 | controlFlow.startWith(taskProcessor)
50 |
51 | assertEquals(1, (getVariableValue(obj = controlFlow, fieldName = "originalTasks") as MutableList<*>).size)
52 | assertEquals(1, (getVariableValue(obj = controlFlow, fieldName = "tasks") as MutableList<*>).size)
53 |
54 | }
55 |
56 | @Test
57 | fun `test adding a task with then`(){
58 | controlFlow.startWith(taskProcessor)
59 | controlFlow.then(rollbackTaskProcessor)
60 |
61 | assertEquals(2, (getVariableValue(obj = controlFlow, fieldName = "originalTasks") as MutableList<*>).size)
62 | assertEquals(2, (getVariableValue(obj = controlFlow, fieldName = "tasks") as MutableList<*>).size)
63 |
64 | }
65 |
66 | @Test
67 | fun `test start() method`() = runTest {
68 | val task1 = mockk()
69 | val param = "test_param"
70 | val expectedResult = TaskStatus.DoneSuccessfully("Task1 Done")
71 |
72 | every { task1.info } returns TaskInfo().apply {
73 | index = 0
74 | name = "Task1"
75 | runIn = Dispatchers.IO
76 | }
77 |
78 | coEvery { task1.doProcess(any()) } returns flowOf(expectedResult)
79 | every { workFlowTracker.started(any()) } just runs
80 | every { workFlowTracker.taskStatus(any(),any(),any()) } just runs
81 | every { workFlowTracker.completed(any()) } just runs
82 |
83 | controlFlow.startWith(task1)
84 | controlFlow.start()
85 |
86 | coVerifyOrder{
87 | workFlowTracker.started(any())
88 | task1.info
89 | task1.doProcess(any())
90 | workFlowTracker.taskStatus(any(),any(),any())
91 | }
92 |
93 | val flow = task1.doProcess(param = param)
94 |
95 | val result = mutableListOf()
96 | flow.collect {
97 | result.add(it)
98 | }
99 |
100 | assert(result.size == 1)
101 | assert(result.first() is TaskStatus.DoneSuccessfully<*>)
102 | assert((result.first() as TaskStatus.DoneSuccessfully<*>).result == expectedResult.result)
103 |
104 | }
105 |
106 |
107 | override fun onStopTest() {
108 | // Reset the main dispatcher after the test completes
109 | Dispatchers.resetMain()
110 | testDispatcher.cleanupTestCoroutines()
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/ControlFlow/src/test/java/io/github/codestarx/DispatcherTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx
2 |
3 | import io.github.codestarx.core.BaseUnitTest
4 | import io.github.codestarx.helper.Dispatcher
5 | import io.github.codestarx.models.ConditionData
6 | import io.github.codestarx.models.TransformData
7 | import io.github.codestarx.status.TaskStatus
8 | import io.mockk.*
9 | import kotlinx.coroutines.flow.Flow
10 | import kotlinx.coroutines.flow.catch
11 | import kotlinx.coroutines.flow.collect
12 | import kotlinx.coroutines.flow.flowOf
13 | import kotlinx.coroutines.test.runTest
14 | import org.junit.Test
15 |
16 | class DispatcherTest: BaseUnitTest() {
17 |
18 | private val dispatcher = object : Dispatcher() {}
19 |
20 | @Test
21 | fun `test launchAwait with action`() = runTest {
22 | val expectedResult = "Success"
23 | val actionMock = mockk< (onContinuation: (Any) -> Unit, onFailure: (Throwable) -> Unit) -> Unit>()
24 |
25 | coEvery { actionMock.invoke(any(),any()) } answers {
26 | val onContinuation = firstArg<(Any) -> Unit>()
27 | onContinuation.invoke(expectedResult)
28 | }
29 |
30 | val result = mutableListOf()
31 | val flow = dispatcher.launchAwait(actionMock)
32 | flow.collect {
33 | result.add(it)
34 | }
35 |
36 | assert(result.size == 1)
37 | assert(result.first() is TaskStatus.DoneSuccessfully<*>)
38 | assert((result.first() as TaskStatus.DoneSuccessfully<*>).result == expectedResult)
39 | }
40 |
41 | @Test
42 | fun `test launchAwait with action and condition`() = runTest {
43 | val expectedResult = "action result"
44 | val actionMock = mockk< (onContinuation: (Any) -> Unit, onFailure: (Throwable) -> Unit) -> Unit>()
45 | val conditionMock = mockk<(Any) -> ConditionData>()
46 |
47 | coEvery { actionMock.invoke(any(),any()) } answers {
48 | val onContinuation = firstArg<(Any) -> Unit>()
49 | onContinuation.invoke(expectedResult)
50 | }
51 | coEvery { conditionMock.invoke(any()) } returns ConditionData(status = false, throwable = Throwable("an error occurs during execution"))
52 |
53 | val result = mutableListOf()
54 | val flow = dispatcher.launchAwait(actionMock, conditionMock)
55 | flow
56 | .catch {
57 | result.add(it)
58 | }
59 | .collect {}
60 |
61 | assert(result.size == 1)
62 | assert(result.first().message == "an error occurs during execution")
63 | }
64 |
65 |
66 | @Test
67 | fun `test launchAwait with action,condition,transformer`() = runTest {
68 | val expectedResult = "action result"
69 | val actionMock = mockk< (onContinuation: (Any) -> Unit, onFailure: (Throwable) -> Unit) -> Unit>()
70 | val conditionMock = mockk<(Any) -> ConditionData>()
71 | val transformerMock = mockk<(Any) -> TransformData>()
72 |
73 | coEvery { actionMock.invoke(any(),any()) } answers {
74 | val onContinuation = firstArg<(Any) -> Unit>()
75 | onContinuation.invoke(expectedResult)
76 | }
77 | coEvery { conditionMock.invoke(any()) } returns ConditionData(status = true)
78 | coEvery { transformerMock.invoke(any()) } returns TransformData(data = 14000704)
79 |
80 | val result = mutableListOf()
81 | dispatcher.launchAwait(actionMock,conditionMock,transformerMock).collect{
82 | result.add(it)
83 | }
84 |
85 | assert(result.size == 1)
86 | assert(result.first() is TaskStatus.DoneSuccessfully<*>)
87 | assert((result.first() as TaskStatus.DoneSuccessfully<*>).result == 14000704 )
88 |
89 | }
90 |
91 | @Test
92 | fun `test launchAwait with action and transformer`() = runTest {
93 | val expectedResult = "action result"
94 | val actionMock = mockk< (onContinuation: (Any) -> Unit, onFailure: (Throwable) -> Unit) -> Unit>()
95 | val transformerMock = mockk<(Any) -> TransformData>()
96 |
97 | coEvery { actionMock.invoke(any(),any()) } answers {
98 | val onContinuation = firstArg<(Any) -> Unit>()
99 | onContinuation.invoke(expectedResult)
100 | }
101 | coEvery { transformerMock.invoke(any()) } returns TransformData(data = 14000704)
102 |
103 | val result = mutableListOf()
104 | dispatcher.launchAwait(actionMock,transformerMock).collect{
105 | result.add(it)
106 | }
107 |
108 | assert(result.size == 1)
109 | assert(result.first() is TaskStatus.DoneSuccessfully<*>)
110 | assert((result.first() as TaskStatus.DoneSuccessfully<*>).result == 14000704 )
111 |
112 | }
113 | @Test
114 | fun `test launchUnitAwait`() = runTest {
115 | val actionMock = mockk< (onContinuation: () -> Unit, onFailure: (Throwable) -> Unit) -> Unit>()
116 |
117 | coEvery { actionMock.invoke(any(),any()) } answers {
118 | val onContinuation = firstArg<() -> Unit>()
119 | onContinuation.invoke()
120 | }
121 |
122 | val result = mutableListOf()
123 | dispatcher.launchUnitAwait(actionMock).collect{
124 | result.add(it)
125 | }
126 |
127 | assert(result.size == 1)
128 | assert(result.first() is TaskStatus.DoneSuccessfully<*>)
129 | assert((result.first() as TaskStatus.DoneSuccessfully<*>).result == Unit )
130 |
131 | }
132 |
133 | @Test
134 | fun `test launchFlow with action and condition`() = runTest {
135 | val expectedResult = "action result"
136 | val actionMock = mockk<(() -> Flow)>()
137 | val conditionMock = mockk<(Any) -> ConditionData>()
138 |
139 | coEvery { actionMock.invoke() } returns flowOf(expectedResult)
140 | coEvery { conditionMock.invoke(any()) } returns ConditionData(status = true)
141 |
142 | val result = mutableListOf()
143 | val flow = dispatcher.launchFlow(actionMock, conditionMock)
144 | flow.collect {
145 | result.add(it)
146 | }
147 |
148 | assert(result.size == 1)
149 | assert(result.first() is TaskStatus.DoneSuccessfully<*> )
150 | assert((result.first() as TaskStatus.DoneSuccessfully<*> ).result == "action result")
151 | }
152 |
153 | @Test
154 | fun `test launchFlow with action,condition and transformer`() = runTest {
155 | val expectedResult = "action result"
156 | val actionMock = mockk<(() -> Flow)>()
157 | val conditionMock = mockk<(Any) -> ConditionData>()
158 | val transformerMock = mockk<(Any) -> TransformData>()
159 |
160 | coEvery { actionMock.invoke() } returns flowOf(expectedResult)
161 | coEvery { conditionMock.invoke(any()) } returns ConditionData(status = true)
162 | coEvery { transformerMock.invoke(any()) } returns TransformData(data = "another data")
163 |
164 | val result = mutableListOf()
165 | val flow = dispatcher.launchFlow(actionMock, conditionMock, transformerMock)
166 | flow.collect {
167 | result.add(it)
168 | }
169 |
170 | assert(result.size == 1)
171 | assert(result.first() is TaskStatus.DoneSuccessfully<*> )
172 | assert((result.first() as TaskStatus.DoneSuccessfully<*> ).result == "another data")
173 | }
174 |
175 | @Test
176 | fun `test launchFlow with action and transformer`() = runTest {
177 | val expectedResult = "action result"
178 | val actionMock = mockk<(() -> Flow)>()
179 | val transformerMock = mockk<(Any) -> TransformData>()
180 |
181 | coEvery { actionMock.invoke() } returns flowOf(expectedResult)
182 | coEvery { transformerMock.invoke(any()) } returns TransformData(data = "another data")
183 |
184 | val result = mutableListOf()
185 | val flow = dispatcher.launchFlow(actionMock, transformerMock)
186 | flow.collect {
187 | result.add(it)
188 | }
189 |
190 | assert(result.size == 1)
191 | assert(result.first() is TaskStatus.DoneSuccessfully<*> )
192 | assert((result.first() as TaskStatus.DoneSuccessfully<*> ).result == "another data")
193 | }
194 |
195 | @Test
196 | fun `test launchFlow with action`() = runTest {
197 | val expectedResult = "action result"
198 | val actionMock = mockk<(() -> Flow)>()
199 |
200 | coEvery { actionMock.invoke() } returns flowOf(expectedResult)
201 |
202 | val result = mutableListOf()
203 | val flow = dispatcher.launchFlow(actionMock)
204 | flow.collect {
205 | result.add(it)
206 | }
207 |
208 | assert(result.size == 1)
209 | assert(result.first() is TaskStatus.DoneSuccessfully<*> )
210 | assert((result.first() as TaskStatus.DoneSuccessfully<*> ).result == "action result")
211 | }
212 |
213 | @Test
214 | fun `test launch with action and condition`() = runTest {
215 | val expectedResult = "action result"
216 | val actionMock = mockk<(() -> Any)>()
217 | val conditionMock = mockk<(Any) -> ConditionData>()
218 |
219 | coEvery { actionMock.invoke() } returns expectedResult
220 | coEvery { conditionMock.invoke(any()) } returns ConditionData(status = false,throwable = Throwable("an error occurs during execution"))
221 |
222 | val result = mutableListOf()
223 | val flow = dispatcher.launch(actionMock, conditionMock)
224 | flow
225 | .catch {
226 | result.add(it)
227 | }
228 | .collect()
229 |
230 | assert(result.size == 1)
231 | assert(result.first().message == "an error occurs during execution")
232 |
233 | }
234 |
235 | @Test
236 | fun `test launch with action`() = runTest {
237 | val expectedResult = Throwable("an error occurs during execution")
238 | val actionMock = mockk<(() -> Any)>()
239 |
240 | coEvery { actionMock.invoke() } answers {
241 | throw expectedResult
242 | }
243 |
244 | val result = mutableListOf()
245 | val flow = dispatcher.launch(actionMock)
246 | flow.catch {
247 | result.add(it)
248 | }.collect()
249 |
250 | assert(result.size == 1)
251 | assert(result.first().message == "an error occurs during execution")
252 |
253 | }
254 |
255 | @Test
256 | fun `test launch with action and transformer`() = runTest {
257 | val expectedResult = "action result"
258 | val actionMock = mockk<(() -> Any)>()
259 | val transformerMock = mockk<(Any) -> TransformData>()
260 |
261 | coEvery { actionMock.invoke() } returns expectedResult
262 | coEvery { transformerMock.invoke(any()) } returns TransformData(data = "another data")
263 |
264 | val result = mutableListOf()
265 | val flow = dispatcher.launch(actionMock, transformerMock)
266 | flow.collect {
267 | result.add(it)
268 | }
269 |
270 | assert(result.size == 1)
271 | assert(result.first() is TaskStatus.DoneSuccessfully<*> )
272 | assert((result.first() as TaskStatus.DoneSuccessfully<*> ).result == "another data")
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/ControlFlow/src/test/java/io/github/codestarx/core/BaseUnitTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.core
2 |
3 |
4 | import io.mockk.MockKAnnotations
5 | import io.mockk.unmockkAll
6 | import org.junit.After
7 | import org.junit.Before
8 | import org.junit.runner.RunWith
9 | import org.junit.runners.BlockJUnit4ClassRunner
10 | import java.lang.reflect.Field
11 |
12 | @RunWith(BlockJUnit4ClassRunner::class)
13 | open class BaseUnitTest {
14 |
15 | open fun onSetUpTest() {}
16 |
17 | open fun onStopTest() {}
18 |
19 | @Before
20 | fun onSetup() {
21 | MockKAnnotations.init(this)
22 | onSetUpTest()
23 | }
24 |
25 | @After
26 | fun onTearDown() {
27 | unmockkAll()
28 | onStopTest()
29 | }
30 |
31 | protected fun getVariableValue(obj: Any, fieldName: String): Any? {
32 | val field: Field = try {
33 | obj.javaClass.getDeclaredField(fieldName)
34 | } catch (e: NoSuchFieldException) {
35 | e.printStackTrace()
36 | return null
37 | }
38 |
39 | field.isAccessible = true
40 | return field.get(obj)
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # ControlFlow
6 |
7 | 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.
8 |
9 | Explore the implementation of the controlflow library through the code samples available in the [ControlFlowDemo](https://github.com/CodeStarX/ControlFlowDemo). repository.
10 |
11 | ## Features
12 |
13 | - **Task Execution Sequencing:** Define and manage a sequence of tasks.
14 | - **Subtasks Execution Sequencing:** Define and manage a sequence of subtasks for each primary task.
15 | - **Rollback Mechanism:** Implement rollback functionalities for tasks that need to revert changes made during their execution.
16 | - **Error Handling:** Handle errors occurring during task execution and initiate rollback processes if required.
17 | - **Automated Data Forwarding:** Each task's output data type is automatically forwarded as the input for the subsequent task by default.
18 |
19 |
20 | ## Installation
21 |
22 | To include ControlFlow in your Android project, add the following dependency to your app's `build.gradle` file:
23 |
24 | ```kotlin
25 |
26 | implementation("io.github.codestarx:control-flow:1.0.0-alpha11")
27 |
28 | repositories {
29 | //..
30 | //..
31 | mavenCentral()
32 | }
33 | ```
34 |
35 |
36 | ### Task Execution with ControlFlow
37 |
38 | 1. **Task Management:** Create instances of tasks inheriting from `Dispatcher` and implementing `TaskProcessor`.
39 | 2. **ControlFlow Class:** Use the `ControlFlow` class to manage task sequences and their execution flow.
40 | 3. **Start Execution:** Begin executing tasks using `start()` method.
41 |
42 |
43 | ## Usage
44 |
45 | ### Task Implementation
46 |
47 | Inherit from Dispatcher: Create tasks by inheriting from the `Dispatcher` class and implementing the `TaskProcessor` properties. For example:
48 |
49 | ```kotlin
50 | class MyTask : Dispatcher(), TaskProcessor {
51 | override val info: TaskInfo
52 | get() = TaskInfo().apply {
53 | index = 0
54 | name = MyTask::class.java.name
55 | runIn = Dispatchers.IO
56 | }
57 |
58 | override suspend fun doProcess(param: Any?): Flow {
59 | // Define the action here
60 | return launchAwait(
61 | action = {
62 | // Perform the action
63 | // ...
64 | },
65 | transformer = {
66 | // The output generated by this function will serve as the input for the subsequent task
67 | // Return TransformData(data= ...)
68 | },
69 | actionCondition = {
70 | // Define conditions for continuation or breaking
71 | // Return ConditionData(status = ... , throwable = ... )
72 | }
73 | )
74 | }
75 | }
76 | ```
77 |
78 |
79 | ### Handling Rollback
80 |
81 | Tasks implementing rollback functionality should also override methods from the `RollbackTaskProcessor` interface, specifying rollback actions.
82 |
83 | Example of a task with rollback:
84 | ```kotlin
85 | class MyRollbackTask : Dispatcher(), RollbackTaskProcessor {
86 | override val info: TaskInfo
87 | get() = TaskInfo().apply {
88 | index = 0
89 | name = MyRollbackTask::class.java.name
90 | runIn = Dispatchers.IO
91 | }
92 |
93 | override val rollbackInfo: RollbackInfo
94 | get() = RollbackInfo().apply {
95 | index = 0
96 | name = MyRollbackTask::class.java.name
97 | runIn = Dispatchers.IO
98 | }
99 |
100 | override suspend fun doProcess(param: Any?): Flow {
101 | // Define the action for the task
102 | return launchAwait(
103 | action = {
104 | // Perform the action
105 | // ...
106 | },
107 | actionCondition = {
108 | // Define conditions for continuation or breaking
109 | // Return ConditionData(status = ... , throwable = ... )
110 | }
111 | )
112 | }
113 |
114 | override suspend fun doRollbackProcess(): Flow {
115 | // Define the rollback action here
116 | return launchAwait(
117 | action = {
118 | // Perform the rollback action
119 | // ...
120 | },
121 | actionCondition = {
122 | // Define conditions for rollback continuation or breaking
123 | // Return ConditionData(status = ... , throwable = ... )
124 | }
125 | )
126 | }
127 | }
128 | ```
129 |
130 | ### Automated Data Forwarding
131 |
132 | Each task's output data type is automatically forwarded as the input for the subsequent task by default.
133 | If you require altering the output data type passed to the next task, utilize the `transformer` method for this purpose.
134 |
135 | Example:
136 |
137 | ```kotlin
138 | class Task : Dispatcher(), TaskProcessor {
139 | override val info: TaskInfo
140 | get() = TaskInfo().apply {
141 | index = 0
142 | name = Task::class.java.name
143 | runIn = Dispatchers.IO
144 | }
145 |
146 | override suspend fun doProcess(param: Any?): Flow {
147 | // Define the action here
148 | return launchAwait(
149 | action = {
150 | // Perform the action
151 | // ...
152 | },
153 | transformer = {
154 | // The output generated by this function will serve as the input for the subsequent task
155 | // Return TransformData(data= ...)
156 | },
157 | actionCondition = {
158 | // Define conditions for continuation or breaking
159 | // Return ConditionData(status = ... , throwable = ... )
160 | }
161 | )
162 | }
163 | }
164 |
165 | ```
166 |
167 | ### Attributes Of Each Task
168 |
169 | The attributes of each task are outlined using the `TaskInfo` class.
170 | 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.
171 |
172 | Example:
173 |
174 | ```kotlin
175 | class Task : Dispatcher(), TaskProcessor {
176 | get() = TaskInfo().apply {
177 | index = 0
178 | name = Task::class.java.name
179 | runIn = Dispatchers.IO
180 | }
181 |
182 | override suspend fun doProcess(param: Any?): Flow {
183 | ...
184 | }
185 |
186 | ```
187 | ### Activate The Retry Mechanism For Each Task
188 |
189 | To activate the Retry mechanism for each task, set the `count` to define the number of retries in case of failure.
190 | Additionally, assign specific `causes`, a list of errors, to trigger retries upon encountering these errors.
191 | Adjust the `delay` value to determine the interval between each retry attempt.
192 |
193 | Example:
194 |
195 | ```kotlin
196 | class Task : Dispatcher(), TaskProcessor {
197 | get() = TaskInfo().apply {
198 | index = 0
199 | name = Task::class.java.name
200 | retry = RetryStrategy().apply {
201 | count = 2
202 | causes = setOf(TimeoutException::class,AnotherException::class,...)
203 | delay = 1000L
204 | }
205 | runIn = Dispatchers.IO
206 | }
207 |
208 | override suspend fun doProcess(param: Any?): Flow {
209 | ...
210 | }
211 |
212 | ```
213 |
214 | ### Attributes Of Each Rollback Task
215 |
216 | The attributes of each task are outlined using the `RollbackInfo` class.
217 | 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.
218 |
219 | Example:
220 |
221 | ```kotlin
222 | class Task : Dispatcher(), RollbackTaskProcessor {
223 | override val info: TaskInfo
224 | get() = TaskInfo().apply {
225 | index = 0
226 | name = Task::class.java.name
227 | runIn = Dispatchers.IO
228 | }
229 |
230 | override val rollbackInfo: RollbackInfo
231 | get() = RollbackInfo().apply {
232 | index = 0
233 | name = Task::class.java.name
234 | runIn = Dispatchers.IO
235 | }
236 |
237 | override suspend fun doProcess(param: Any?): Flow {
238 | ...
239 | }
240 |
241 | override suspend fun doRollbackProcess(): Flow {
242 | ...
243 | }
244 |
245 | ```
246 |
247 | ### Activate The Retry Mechanism For Each Rollback Task
248 |
249 | To activate the Retry mechanism for each rollback task, set the `count` to define the number of retries in case of failure.
250 | Additionally, assign specific `causes`, a list of errors, to trigger retries upon encountering these errors.
251 | Adjust the `delay` value to determine the interval between each retry attempt.
252 |
253 | Example:
254 |
255 | ```kotlin
256 | class Task : Dispatcher(), RollbackTaskProcessor {
257 | override val info: TaskInfo
258 | get() = ...
259 |
260 | override val rollbackInfo: RollbackInfo
261 | get() = RollbackInfo().apply {
262 | index = 0
263 | name = Task::class.java.name
264 | retry = RetryStrategy().apply {
265 | count = 2
266 | causes = setOf(TimeoutException::class,AnotherException::class,...)
267 | delay = 1000L
268 | }
269 | runIn = Dispatchers.IO
270 | }
271 |
272 | override suspend fun doProcess(param: Any?): Flow {
273 | ...
274 | }
275 |
276 | override suspend fun doRollbackProcess(): Flow {
277 | ...
278 | }
279 |
280 | ```
281 |
282 | ## ControlFlow Class
283 | `ControlFlow` manages the execution sequence of tasks and potential rollback actions.
284 | It orchestrates the execution, rollback, completion, and error handling of tasks and their rollbacks.
285 | This class offers a structured way to manage a series of tasks and handles their execution flow and potential rollbacks.
286 |
287 | ### Running Control Flow
288 |
289 | Example usage:
290 |
291 | ```kotlin
292 | // Create a ControlFlow instance
293 | val controlFlow = ControlFlow(object : WorkFlowTracker {
294 | // Implement work Flow callback methods
295 | })
296 |
297 | // Define your tasks
298 | controlFlow.startWith(MyTask())
299 | controlFlow.then(AnotherTask())
300 | controlFlow.then(AnotherTask())
301 |
302 | // Set up TaskStatusTracker if needed
303 | controlFlow.useTaskStatusTracker(object : TaskStatusTracker {
304 | // Implement callback methods
305 | })
306 |
307 | // Set up RollbackStatusTracker if needed
308 | controlFlow.useRollbackStatusTracker(object : RollbackStatusTracker {
309 | // Implement callback methods
310 | })
311 |
312 | // Start executing tasks
313 | controlFlow.start()
314 | ```
315 |
316 | ### Subtasks Execution
317 | To incorporate subtasks for each task, you can define their implementation as outlined below:
318 |
319 | Example usage:
320 |
321 | ```kotlin
322 | // Create a ControlFlow instance
323 | val controlFlow = ControlFlow(object : WorkFlowTracker {
324 | // Implement work Flow callback methods
325 | })
326 |
327 | // Define your tasks
328 | controlFlow.startWith(MyTask().apply{
329 | // Define your subtasks
330 | then(subtask= MySubtask())
331 | then(subtask= AnotherSubtask())
332 | })
333 | controlFlow.then(AnotherTask().apply{
334 | // Define your subtasks
335 | then(subtask= MySubtask())
336 | then(subtask= AnotherSubtask())
337 | })
338 |
339 | // Set up TaskStatusTracker if needed
340 | controlFlow.useTaskStatusTracker(object : TaskStatusTracker {
341 | // Implement callback methods
342 | })
343 |
344 | // Set up RollbackStatusTracker if needed
345 | controlFlow.useRollbackStatusTracker(object : RollbackStatusTracker {
346 | // Implement callback methods
347 | })
348 |
349 | // Start executing tasks
350 | controlFlow.start()
351 | ```
352 |
353 | ### Control-Flow Method Details
354 | ```kotlin
355 | /**
356 | * Add the first task to the control flow sequence.
357 | * @param first task to be added to the control flow sequence.
358 | */
359 | startWith(first: TaskProcessor)
360 | ```
361 | ```kotlin
362 | /**
363 | * Adds the next task to the control flow sequence.
364 | * @param next The subsequent task to be added to the control flow sequence.
365 | */
366 | then(next: TaskProcessor)
367 | ```
368 | ```kotlin
369 | /**
370 | * Starts executing the tasks in the control flow sequence.
371 | * @param runAutomaticallyRollback Set to true if you want tasks to automatically rollback on failure.
372 | */
373 | start(runAutomaticallyRollback: Boolean = false)
374 | ```
375 | ```kotlin
376 | /**
377 | * Starts executing tasks from a specific task name in the sequence.
378 | * @param taskName The name of the task from which to start the execution.
379 | */
380 | startFrom(taskName: String)
381 | ```
382 | ```kotlin
383 | /**
384 | * Starts executing tasks from a specific task index in the sequence.
385 | * @param taskIndex The index of the task from which to start the execution.
386 | */
387 | startFrom(taskIndex: Int)
388 | ```
389 | ```kotlin
390 | /**
391 | * Restarts the control flow from the beginning.
392 | */
393 | restart()
394 | ```
395 | ```kotlin
396 | /**
397 | * Starts executing the rollback tasks in the control flow sequence.
398 | */
399 | startRollback()
400 | ```
401 | ```kotlin
402 | /**
403 | * Initiates the rollback process from a specific task name in the rollback sequence.
404 | * @param taskName The name of the task from which to start the rollback process.
405 | */
406 | startRollbackFrom(taskName: String)
407 | ```
408 | ```kotlin
409 | /**
410 | * Initiates the rollback process from a specific task index in the rollback sequence.
411 | * @param taskIndex The index of the task from which to start the rollback process.
412 | */
413 | startRollbackFrom(taskIndex: Int)
414 | ```
415 | ```kotlin
416 | /**
417 | * Restarts the rollback process.
418 | */
419 | restartRollback()
420 | ```
421 | ```kotlin
422 | /**
423 | * Associates a callback for the main task execution.
424 | * @param callBack The callback to be associated with the main task execution.
425 | */
426 | useTaskStatusTracker(callBack: TaskStatusTracker)
427 | ```
428 | ```kotlin
429 | /**
430 | * Associates a callback for the rollback task execution.
431 | * @param callBack The callback to be associated with the rollback task execution.
432 | */
433 | useRollbackStatusTracker( callBack: RollbackStatusTracker)
434 | ```
435 | ```kotlin
436 | /**
437 | * Stops and cleans up the resources associated with the task processing system.
438 | * This method sets various internal variables to null, effectively releasing
439 | * the references to the task-related objects, job instances, and other components.
440 | */
441 | fun stop()
442 | ```
443 |
444 | ## Dispatcher Class
445 |
446 | The `Dispatcher` class serves as a utility to execute asynchronous actions, manage errors, and handle various scenarios using coroutines. This class offers a range of methods to facilitate asynchronous operations and streamline error handling within tasks.
447 |
448 | ### Method Details
449 |
450 | #### `safeLauncher`
451 |
452 | - **Usage**: Executes a provided coroutine block within the `viewModelScope`, managing potential exceptions via a `CoroutineExceptionHandler`.
453 | - **Example**:
454 | ```kotlin
455 | safeLauncher {
456 | // Coroutine block to execute
457 | // ...
458 | }
459 | ```
460 |
461 | #### `launchAwait`
462 |
463 | - **Usage**: Executes an asynchronous action and emits the result or errors via a Flow, based on specified custom conditions.
464 | - **Example**:
465 |
466 | ```kotlin
467 | launchAwait(
468 | // Action to execute asynchronously with callbacks for continuation and failure
469 | { onContinuation, onFailure ->
470 | // Invoke the action and handle results
471 | // Call onContinuation with the result to continue or onFailure with an error to break
472 | },
473 | // Condition to check if continuation or breaking is required based on the result
474 | { result ->
475 | // Define conditions for rollback continuation or breaking
476 | // Return ConditionData(status = ... , throwable = ... )
477 | }
478 | )
479 | ```
480 |
481 | #### `launchUnitAwait`
482 |
483 | - **Usage**: Executes an asynchronous action that returns a Unit result and emits the status via a Flow.
484 | - **Example**:
485 | ```kotlin
486 | launchUnitAwait { onContinuation, onFailure ->
487 | // Invoke the action and handle results
488 | // Call onContinuation to indicate successful completion or onFailure with an error
489 | }
490 | ```
491 |
492 | #### `launchFlow`
493 |
494 | - **Usage**: Executes an asynchronous action returning a Flow, evaluates conditions for success or breaking, and emits the status accordingly.
495 | - **Example**:
496 | ```kotlin
497 | launchFlow(
498 | // Action returning a Flow
499 | { flowAction() },
500 | // Condition to check if continuation or breaking is required based on the Flow's result
501 | { result ->
502 | // Define conditions for rollback continuation or breaking
503 | // Return ConditionData(status = ... , throwable = ... )
504 | }
505 | )
506 | ```
507 |
508 | #### `launch`
509 |
510 | - **Usage**: Executes a synchronous action, evaluates conditions for success or breaking, and emits the status via a Flow.
511 | - **Example**:
512 | ```kotlin
513 | launch(
514 | // Synchronous action to execute
515 | { syncAction() },
516 | // Condition to check if continuation or breaking is required based on the action's result
517 | { result ->
518 | // Define conditions for rollback continuation or breaking
519 | // Return ConditionData(status = ... , throwable = ... )
520 | }
521 | )
522 | ```
523 |
524 | ## Documentation
525 |
526 | For detailed information about classes, methods, and functionalities provided by ControlFlow, refer to the inline comments in the source code.
527 |
528 | ## Contributing
529 |
530 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
531 |
532 | 1. Fork the Project
533 | 2. Create your Feature Branch (`git checkout -b feature/YourFeatureName`)
534 | 3. Commit your Changes (`git commit -m 'Add some YourFeatureName'`)
535 | 4. Push to the Branch (`git push origin feature/YourFeatureName`)
536 | 5. Open a Pull Request
537 |
538 | ## LICENSE
539 |
540 | The Apache Software License, Version 2.0
541 |
542 | http://www.apache.org/licenses/LICENSE-2.0.txt
543 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | id("org.jetbrains.kotlin.android")
4 | }
5 |
6 | android {
7 | namespace = "io.github.codestarx.controlflowdevelop"
8 | compileSdk = 34
9 |
10 | defaultConfig {
11 | applicationId = "io.github.codestarx.controlflowdevelop"
12 | minSdk = 21
13 | targetSdk = 33
14 | versionCode = 1
15 | versionName = "1.0"
16 |
17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18 | vectorDrawables {
19 | useSupportLibrary = true
20 | }
21 | }
22 |
23 | buildTypes {
24 | release {
25 | isMinifyEnabled = true
26 | proguardFiles(
27 | getDefaultProguardFile("proguard-android-optimize.txt"),
28 | "proguard-rules.pro"
29 | )
30 | }
31 | }
32 | compileOptions {
33 | sourceCompatibility = JavaVersion.VERSION_17
34 | targetCompatibility = JavaVersion.VERSION_17
35 | }
36 | kotlinOptions {
37 | jvmTarget = "17"
38 | }
39 | }
40 |
41 | dependencies {
42 | implementation("androidx.core:core-ktx:1.7.0")
43 | implementation("androidx.appcompat:appcompat:1.6.1")
44 |
45 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/codestarx/controlflowdevelop/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package io.github.codestarx.controlflowdevelop
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 |
6 | class MainActivity : ComponentActivity() {
7 | override fun onCreate(savedInstanceState: Bundle?) {
8 | super.onCreate(savedInstanceState)
9 |
10 | }
11 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeStarX/ControlFlow/d76e3fd6f00ed713dc5412eb0a07bae4749c174e/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeStarX/ControlFlow/d76e3fd6f00ed713dc5412eb0a07bae4749c174e/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeStarX/ControlFlow/d76e3fd6f00ed713dc5412eb0a07bae4749c174e/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeStarX/ControlFlow/d76e3fd6f00ed713dc5412eb0a07bae4749c174e/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeStarX/ControlFlow/d76e3fd6f00ed713dc5412eb0a07bae4749c174e/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeStarX/ControlFlow/d76e3fd6f00ed713dc5412eb0a07bae4749c174e/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeStarX/ControlFlow/d76e3fd6f00ed713dc5412eb0a07bae4749c174e/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeStarX/ControlFlow/d76e3fd6f00ed713dc5412eb0a07bae4749c174e/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeStarX/ControlFlow/d76e3fd6f00ed713dc5412eb0a07bae4749c174e/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeStarX/ControlFlow/d76e3fd6f00ed713dc5412eb0a07bae4749c174e/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ControlFlowDevelop
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/attach/attach.xml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeStarX/ControlFlow/d76e3fd6f00ed713dc5412eb0a07bae4749c174e/attach/attach.xml
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id("com.android.application") version "8.2.0" apply false
4 | id("com.android.library") version "8.2.0" apply false
5 | id("org.jetbrains.kotlin.android") version "1.9.20" apply false
6 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 |
25 | mavenCentralUsername=xxx
26 | mavenCentralPassword=xxx
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeStarX/ControlFlow/d76e3fd6f00ed713dc5412eb0a07bae4749c174e/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Dec 29 19:42:51 IRST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | buildscript {
8 | repositories {
9 | mavenCentral()
10 | maven {
11 | url = uri("https://storage.googleapis.com/r8-releases/raw")
12 | }
13 | }
14 | dependencies {
15 | classpath("com.android.tools:r8:8.2.33")
16 | }
17 | }
18 |
19 | }
20 | dependencyResolutionManagement {
21 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
22 | repositories {
23 | google()
24 | mavenCentral()
25 | }
26 | }
27 |
28 | rootProject.name = "ControlFlowDevelop"
29 | include(":app")
30 | include(":ControlFlow")
31 |
--------------------------------------------------------------------------------