Skip to content

Commit 88a3e77

Browse files
committed
refactor: Remove duplicate code
1 parent 916289f commit 88a3e77

File tree

8 files changed

+75
-66
lines changed

8 files changed

+75
-66
lines changed

android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ForegroundService.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import com.pravera.flutter_foreground_task.models.*
2222
import com.pravera.flutter_foreground_task.utils.ForegroundServiceUtils
2323
import com.pravera.flutter_foreground_task.utils.PluginUtils
2424
import kotlinx.coroutines.*
25+
import kotlinx.coroutines.flow.MutableStateFlow
26+
import kotlinx.coroutines.flow.asStateFlow
27+
import kotlinx.coroutines.flow.update
2528
import java.util.*
2629

2730
/**
@@ -40,15 +43,14 @@ class ForegroundService : Service() {
4043
private const val ACTION_NOTIFICATION_DISMISSED = "onNotificationDismissed"
4144
private const val INTENT_DATA_FIELD_NAME = "data"
4245

43-
/** Returns whether the foreground service is running. */
44-
var isRunningService = false
45-
private set
46+
private val _isRunningServiceState = MutableStateFlow(false)
47+
val isRunningServiceState = _isRunningServiceState.asStateFlow()
4648

4749
private var foregroundTask: ForegroundTask? = null
4850
private var taskLifecycleListeners = ForegroundTaskLifecycleListeners()
4951

5052
fun sendData(data: Any?) {
51-
if (isRunningService) {
53+
if (isRunningServiceState.value) {
5254
foregroundTask?.invokeMethod(ACTION_RECEIVE_DATA, data)
5355
}
5456
}
@@ -247,14 +249,15 @@ class ForegroundService : Service() {
247249
releaseLockMode()
248250
acquireLockMode()
249251

250-
isRunningService = true
252+
_isRunningServiceState.update { true }
251253
}
252254

253255
private fun stopForegroundService() {
254256
releaseLockMode()
255257
stopForeground(true)
256258
stopSelf()
257-
isRunningService = false
259+
260+
_isRunningServiceState.update { false }
258261
}
259262

260263
@RequiresApi(Build.VERSION_CODES.O)

android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ForegroundServiceManager.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import com.pravera.flutter_foreground_task.models.NotificationOptions
2121
class ForegroundServiceManager {
2222
/** Start the foreground service. */
2323
fun start(context: Context, arguments: Any?) {
24-
if (ForegroundService.isRunningService) {
24+
if (isRunningService()) {
2525
throw ServiceAlreadyStartedException()
2626
}
2727

@@ -37,7 +37,7 @@ class ForegroundServiceManager {
3737

3838
/** Restart the foreground service. */
3939
fun restart(context: Context) {
40-
if (!ForegroundService.isRunningService) {
40+
if (!isRunningService()) {
4141
throw ServiceNotStartedException()
4242
}
4343

@@ -48,7 +48,7 @@ class ForegroundServiceManager {
4848

4949
/** Update the foreground service. */
5050
fun update(context: Context, arguments: Any?) {
51-
if (!ForegroundService.isRunningService) {
51+
if (!isRunningService()) {
5252
throw ServiceNotStartedException()
5353
}
5454

@@ -63,7 +63,7 @@ class ForegroundServiceManager {
6363

6464
/** Stop the foreground service. */
6565
fun stop(context: Context) {
66-
if (!ForegroundService.isRunningService) {
66+
if (!isRunningService()) {
6767
throw ServiceNotStartedException()
6868
}
6969

@@ -84,5 +84,5 @@ class ForegroundServiceManager {
8484
}
8585

8686
/** Returns whether the foreground service is running. */
87-
fun isRunningService(): Boolean = ForegroundService.isRunningService
87+
fun isRunningService(): Boolean = ForegroundService.isRunningServiceState.value
8888
}

lib/flutter_foreground_task.dart

Lines changed: 23 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import 'models/notification_options.dart';
1717
import 'models/notification_permission.dart';
1818
import 'models/service_request_result.dart';
1919
import 'task_handler.dart';
20+
import 'utils/utility.dart';
2021

2122
export 'errors/service_already_started_exception.dart';
2223
export 'errors/service_not_initialized_exception.dart';
@@ -101,11 +102,11 @@ class FlutterForegroundTask {
101102
List<NotificationButton>? notificationButtons,
102103
Function? callback,
103104
}) async {
104-
if (!isInitialized) {
105-
return ServiceRequestResult.error(ServiceNotInitializedException());
106-
}
107-
108105
try {
106+
if (!isInitialized) {
107+
throw ServiceNotInitializedException();
108+
}
109+
109110
if (await isRunningService) {
110111
throw ServiceAlreadyStartedException();
111112
}
@@ -123,25 +124,7 @@ class FlutterForegroundTask {
123124
);
124125

125126
if (!skipServiceResponseCheck) {
126-
final Stopwatch stopwatch = Stopwatch()..start();
127-
bool isStarted = false;
128-
await Future.doWhile(() async {
129-
isStarted = await isRunningService;
130-
131-
// official doc: Once the service has been created, the service must call its startForeground() method within five seconds.
132-
// ref: https://developer.android.com/guide/components/services#StartingAService
133-
if (isStarted || stopwatch.elapsedMilliseconds > 5 * 1000) {
134-
return false;
135-
} else {
136-
await Future.delayed(const Duration(milliseconds: 100));
137-
return true;
138-
}
139-
});
140-
141-
// no response :(
142-
if (!isStarted) {
143-
throw ServiceTimeoutException();
144-
}
127+
await checkServiceStateChange(target: true);
145128
}
146129

147130
return ServiceRequestResult.success();
@@ -204,25 +187,7 @@ class FlutterForegroundTask {
204187
await _platform.stopService();
205188

206189
if (!skipServiceResponseCheck) {
207-
final Stopwatch stopwatch = Stopwatch()..start();
208-
bool isStopped = false;
209-
await Future.doWhile(() async {
210-
isStopped = !(await isRunningService);
211-
212-
// official doc: Once the service has been created, the service must call its startForeground() method within five seconds.
213-
// ref: https://developer.android.com/guide/components/services#StartingAService
214-
if (isStopped || stopwatch.elapsedMilliseconds > 5 * 1000) {
215-
return false;
216-
} else {
217-
await Future.delayed(const Duration(milliseconds: 100));
218-
return true;
219-
}
220-
});
221-
222-
// no response :(
223-
if (!isStopped) {
224-
throw ServiceTimeoutException();
225-
}
190+
await checkServiceStateChange(target: false);
226191
}
227192

228193
return ServiceRequestResult.success();
@@ -231,6 +196,22 @@ class FlutterForegroundTask {
231196
}
232197
}
233198

199+
@visibleForTesting
200+
static Future<void> checkServiceStateChange({required bool target}) async {
201+
// official doc: Once the service has been created, the service must call its startForeground() method within 5 seconds.
202+
// ref: https://developer.android.com/guide/components/services#StartingAService
203+
final bool isCompleted = await Utility.instance.completedWithinDeadline(
204+
deadline: const Duration(seconds: 5),
205+
future: () async {
206+
return target == await isRunningService;
207+
},
208+
);
209+
210+
if (!isCompleted) {
211+
throw ServiceTimeoutException();
212+
}
213+
}
214+
234215
/// Returns whether the foreground service is running.
235216
static Future<bool> get isRunningService => _platform.isRunningService;
236217

lib/models/notification_button.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import 'dart:ui';
22

3+
import 'package:flutter_foreground_task/utils/color_extension.dart';
4+
35
/// The button to display in the notification.
46
class NotificationButton {
57
/// Constructs an instance of [NotificationButton].
@@ -21,10 +23,7 @@ class NotificationButton {
2123

2224
/// Returns the data fields of [NotificationButton] in JSON format.
2325
Map<String, dynamic> toJson() {
24-
String? textColorRgb;
25-
if (textColor != null) {
26-
textColorRgb = '${textColor!.red},${textColor!.green},${textColor!.blue}';
27-
}
26+
final String? textColorRgb = textColor?.toRgbString;
2827

2928
return {
3029
'id': id,

lib/models/notification_icon_data.dart

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import 'dart:ui';
22

3+
import 'package:flutter_foreground_task/utils/color_extension.dart';
4+
35
/// The resource type of the notification icon.
46
enum ResourceType {
57
/// A resources in the drawable folder.
@@ -48,11 +50,7 @@ class NotificationIconData {
4850

4951
/// Returns the data fields of [NotificationIconData] in JSON format.
5052
Map<String, dynamic> toJson() {
51-
String? backgroundColorRgb;
52-
if (backgroundColor != null) {
53-
backgroundColorRgb =
54-
'${backgroundColor!.red},${backgroundColor!.green},${backgroundColor!.blue}';
55-
}
53+
final String? backgroundColorRgb = backgroundColor?.toRgbString;
5654

5755
return {
5856
'resType': resType.toString().split('.').last,

lib/ui/with_foreground_task.dart

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@ class WithForegroundTask extends StatefulWidget {
99
/// A child widget that contains the [Scaffold] widget.
1010
final Widget child;
1111

12-
const WithForegroundTask({
13-
Key? key,
14-
required this.child,
15-
}) : super(key: key);
12+
const WithForegroundTask({super.key, required this.child});
1613

1714
@override
1815
State<StatefulWidget> createState() => _WithForegroundTaskState();

lib/utils/color_extension.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import 'dart:ui';
2+
3+
extension ColorExtension on Color {
4+
String get toRgbString => '$red,$green,$blue';
5+
}

lib/utils/utility.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
final class Utility {
2+
Utility._();
3+
4+
static Utility instance = Utility._();
5+
6+
Future<bool> completedWithinDeadline({
7+
required Duration deadline,
8+
required Future<bool> Function() future,
9+
Duration tick = const Duration(milliseconds: 100),
10+
}) async {
11+
final Stopwatch stopwatch = Stopwatch()..start();
12+
bool completed = false;
13+
await Future.doWhile(() async {
14+
completed = await future();
15+
if (completed ||
16+
stopwatch.elapsedMilliseconds > deadline.inMilliseconds) {
17+
return false;
18+
} else {
19+
await Future.delayed(tick);
20+
return true;
21+
}
22+
});
23+
24+
return completed;
25+
}
26+
}

0 commit comments

Comments
 (0)