Feat: Stellt den BackgroundManager auf IsolateManager um
This commit is contained in:
@@ -1,19 +1,18 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:isolate_manager/isolate_manager.dart';
|
||||
import 'package:workmanager/workmanager.dart';
|
||||
|
||||
import '../Tasks/generate_transactions_task.dart';
|
||||
import '../Tasks/task.dart';
|
||||
import 'port_controller.dart';
|
||||
import '../Tasks/workers.dart';
|
||||
import '../Tasks/workmanager_workers.dart';
|
||||
|
||||
/// Erstellt Hintergrundtasks und führt diese aus
|
||||
class BackgroundTaskController {
|
||||
/// Erstellt eine neue Instanz dieser Klasse
|
||||
BackgroundTaskController() {
|
||||
if (Platform.isAndroid) {
|
||||
if (!kIsWeb && Platform.isAndroid) {
|
||||
unawaited(Workmanager().initialize(callbackDispatcher));
|
||||
unawaited(
|
||||
Workmanager().registerPeriodicTask(
|
||||
@@ -25,47 +24,12 @@ class BackgroundTaskController {
|
||||
);
|
||||
} else {
|
||||
unawaited(
|
||||
Isolate.run(() {
|
||||
unawaited(_runTask(
|
||||
GenerateTransactionsTask(),
|
||||
const Duration(minutes: 1),
|
||||
const Duration(minutes: 30),
|
||||
));
|
||||
IsolateManager.runFunction(runTask, {
|
||||
'taskName': 'generate_transactions',
|
||||
'initialDelayMinutes': 1,
|
||||
'frequencyMinutes': 30,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runTask(
|
||||
final Task task,
|
||||
final Duration initialDelay,
|
||||
final Duration frequency,
|
||||
) async {
|
||||
await Future.delayed(initialDelay);
|
||||
|
||||
final RootIsolateToken? rootIsolateToken = await PortController()
|
||||
.getRootIsolateToken();
|
||||
|
||||
if (rootIsolateToken != null) {
|
||||
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
|
||||
await GenerateTransactionsTask().execute();
|
||||
}
|
||||
|
||||
await Future.delayed(frequency);
|
||||
|
||||
unawaited(_runTask(task, initialDelay, frequency));
|
||||
}
|
||||
}
|
||||
|
||||
/// Die Funktion wird von Hintergrundtasks ausgerufen, um diese auszuführen
|
||||
@pragma('vm:entry-point')
|
||||
void callbackDispatcher() {
|
||||
Workmanager().executeTask((final task, final inputData) {
|
||||
switch (task) {
|
||||
case 'generate_transactions':
|
||||
return GenerateTransactionsTask().execute();
|
||||
}
|
||||
|
||||
return Future.value(true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,24 +2,28 @@ import 'dart:async';
|
||||
import 'dart:isolate';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
/// Ein PortController mit verschiedenen FUnktionen zur KOmmunikations zwischen
|
||||
/// Ein PortController mit verschiedenen Funktionen zur Kommunikation zwischen
|
||||
/// main und anderen Isolates
|
||||
class PortController {
|
||||
/// Gibt eine Instanz dieser Klasse zurück
|
||||
factory PortController() => _instance;
|
||||
|
||||
PortController._internal() {
|
||||
if (ServicesBinding.rootIsolateToken != null) {
|
||||
if (!kIsWeb && ServicesBinding.rootIsolateToken != null) {
|
||||
_registerRootIsolateTokenSender();
|
||||
}
|
||||
}
|
||||
|
||||
static final PortController _instance = PortController._internal();
|
||||
final Logger _logger = Logger();
|
||||
|
||||
/// Fügt einen Port mit [name] zum NameServer hinzu
|
||||
void addPort(final SendPort sendPort, final String name) {
|
||||
IsolateNameServer.removePortNameMapping(name);
|
||||
IsolateNameServer.registerPortWithName(sendPort, name);
|
||||
}
|
||||
|
||||
@@ -29,6 +33,8 @@ class PortController {
|
||||
|
||||
/// Gibt das [RootIsolateToken] der main-Isolate zurück
|
||||
Future<RootIsolateToken?> getRootIsolateToken() async {
|
||||
_logger.d('Trying to retrieve RootIsolateToken...');
|
||||
|
||||
final ReceivePort receivePort = ReceivePort();
|
||||
|
||||
final SendPort? rootPort = IsolateNameServer.lookupPortByName(
|
||||
@@ -36,21 +42,26 @@ class PortController {
|
||||
);
|
||||
|
||||
if (rootPort == null) {
|
||||
_logger.e("Couldn't get Port from IsolateNameServer!");
|
||||
receivePort.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
_logger.i('Sending communication attempt...');
|
||||
rootPort.send(receivePort.sendPort);
|
||||
|
||||
try {
|
||||
final dynamic message = await receivePort.first;
|
||||
|
||||
if (message is RootIsolateToken) {
|
||||
_logger.i('Got RootIsolateToken, returning...');
|
||||
return message;
|
||||
}
|
||||
|
||||
_logger.w("Couldn't get RootIsolateToken!");
|
||||
return null;
|
||||
} finally {
|
||||
_logger.i('Closing receivePort...');
|
||||
receivePort.close();
|
||||
}
|
||||
}
|
||||
@@ -58,6 +69,8 @@ class PortController {
|
||||
void _registerRootIsolateTokenSender() {
|
||||
final ReceivePort receivePort = ReceivePort()
|
||||
..listen((final value) {
|
||||
_logger.d('Received Message with $value');
|
||||
|
||||
if (value is SendPort) {
|
||||
value.send(ServicesBinding.rootIsolateToken);
|
||||
}
|
||||
|
||||
@@ -102,5 +102,6 @@ class AppDatabase extends _$AppDatabase {
|
||||
sqlite3Wasm: Uri.parse('sqlite3.wasm'),
|
||||
driftWorker: Uri.parse('drift_worker.js'),
|
||||
),
|
||||
native: const DriftNativeOptions(shareAcrossIsolates: true)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -577,8 +577,7 @@ class RecurringTransaction extends DataClass
|
||||
String toString() {
|
||||
return (StringBuffer('RecurringTransaction(')
|
||||
..write('id: $id, ')
|
||||
..write('name: $name, ')
|
||||
..write('startDate: $startDate, ')..write(
|
||||
..write('name: $name, ')..write('startDate: $startDate, ')..write(
|
||||
'timeFrame: $timeFrame, ')..write('amount: $amount, ')..write(
|
||||
'accountId: $accountId, ')..write('updatedAt: $updatedAt')
|
||||
..write(')'))
|
||||
@@ -702,8 +701,7 @@ class RecurringTransactionsCompanion
|
||||
String toString() {
|
||||
return (StringBuffer('RecurringTransactionsCompanion(')
|
||||
..write('id: $id, ')
|
||||
..write('name: $name, ')
|
||||
..write('startDate: $startDate, ')..write(
|
||||
..write('name: $name, ')..write('startDate: $startDate, ')..write(
|
||||
'timeFrame: $timeFrame, ')..write('amount: $amount, ')..write(
|
||||
'accountId: $accountId, ')..write('updatedAt: $updatedAt')
|
||||
..write(')'))
|
||||
@@ -1076,11 +1074,14 @@ class Transaction extends DataClass implements Insertable<Transaction> {
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('Transaction(')
|
||||
..write('id: $id, ')..write('name: $name, ')..write(
|
||||
'date: $date, ')..write('amount: $amount, ')..write(
|
||||
'checked: $checked, ')..write('accountId: $accountId, ')..write(
|
||||
'recurringTransactionId: $recurringTransactionId, ')..write(
|
||||
'updatedAt: $updatedAt')
|
||||
..write('id: $id, ')
|
||||
..write('name: $name, ')
|
||||
..write('date: $date, ')
|
||||
..write('amount: $amount, ')
|
||||
..write('checked: $checked, ')
|
||||
..write('accountId: $accountId, ')
|
||||
..write('recurringTransactionId: $recurringTransactionId, ')
|
||||
..write('updatedAt: $updatedAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
@@ -1220,11 +1221,15 @@ class TransactionsCompanion extends UpdateCompanion<Transaction> {
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('TransactionsCompanion(')
|
||||
..write('id: $id, ')..write('name: $name, ')..write(
|
||||
'date: $date, ')..write('amount: $amount, ')..write(
|
||||
'checked: $checked, ')..write('accountId: $accountId, ')..write(
|
||||
'recurringTransactionId: $recurringTransactionId, ')..write(
|
||||
'updatedAt: $updatedAt')..write(')'))
|
||||
..write('id: $id, ')
|
||||
..write('name: $name, ')
|
||||
..write('date: $date, ')
|
||||
..write('amount: $amount, ')
|
||||
..write('checked: $checked, ')
|
||||
..write('accountId: $accountId, ')
|
||||
..write('recurringTransactionId: $recurringTransactionId, ')
|
||||
..write('updatedAt: $updatedAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -1233,9 +1238,7 @@ class $SyncLogTable extends SyncLog with TableInfo<$SyncLogTable, SyncLogData> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
|
||||
$SyncLogTable(this.attachedDatabase, [this._alias]);
|
||||
|
||||
static const VerificationMeta _idMeta = const VerificationMeta('id');
|
||||
@override
|
||||
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||
@@ -1282,19 +1285,16 @@ class $SyncLogTable extends SyncLog with TableInfo<$SyncLogTable, SyncLogData> {
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: currentDateAndTime,
|
||||
);
|
||||
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [id, type, description, updatedAt];
|
||||
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'sync_log';
|
||||
|
||||
@override
|
||||
VerificationContext validateIntegrity(Insertable<SyncLogData> instance, {
|
||||
VerificationContext validateIntegrity(
|
||||
Insertable<SyncLogData> instance, {
|
||||
bool isInserting = false,
|
||||
}) {
|
||||
final context = VerificationContext();
|
||||
@@ -1322,7 +1322,6 @@ class $SyncLogTable extends SyncLog with TableInfo<$SyncLogTable, SyncLogData> {
|
||||
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {id};
|
||||
|
||||
@override
|
||||
SyncLogData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
@@ -1369,14 +1368,12 @@ class SyncLogData extends DataClass implements Insertable<SyncLogData> {
|
||||
|
||||
/// Wann dieser SyncLog das letzte mal geupdated wurde
|
||||
final DateTime updatedAt;
|
||||
|
||||
const SyncLogData({
|
||||
required this.id,
|
||||
required this.type,
|
||||
required this.description,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
@@ -1398,7 +1395,8 @@ class SyncLogData extends DataClass implements Insertable<SyncLogData> {
|
||||
);
|
||||
}
|
||||
|
||||
factory SyncLogData.fromJson(Map<String, dynamic> json, {
|
||||
factory SyncLogData.fromJson(
|
||||
Map<String, dynamic> json, {
|
||||
ValueSerializer? serializer,
|
||||
}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
@@ -1411,7 +1409,6 @@ class SyncLogData extends DataClass implements Insertable<SyncLogData> {
|
||||
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
@@ -1428,14 +1425,12 @@ class SyncLogData extends DataClass implements Insertable<SyncLogData> {
|
||||
SyncLogTypeEnum? type,
|
||||
String? description,
|
||||
DateTime? updatedAt,
|
||||
}) =>
|
||||
SyncLogData(
|
||||
}) => SyncLogData(
|
||||
id: id ?? this.id,
|
||||
type: type ?? this.type,
|
||||
description: description ?? this.description,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
);
|
||||
|
||||
SyncLogData copyWithCompanion(SyncLogCompanion data) {
|
||||
return SyncLogData(
|
||||
id: data.id.present ? data.id.value : this.id,
|
||||
@@ -1450,15 +1445,16 @@ class SyncLogData extends DataClass implements Insertable<SyncLogData> {
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('SyncLogData(')
|
||||
..write('id: $id, ')..write('type: $type, ')..write(
|
||||
'description: $description, ')..write('updatedAt: $updatedAt')..write(
|
||||
')'))
|
||||
..write('id: $id, ')
|
||||
..write('type: $type, ')
|
||||
..write('description: $description, ')
|
||||
..write('updatedAt: $updatedAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(id, type, description, updatedAt);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
@@ -1474,21 +1470,18 @@ class SyncLogCompanion extends UpdateCompanion<SyncLogData> {
|
||||
final Value<SyncLogTypeEnum> type;
|
||||
final Value<String> description;
|
||||
final Value<DateTime> updatedAt;
|
||||
|
||||
const SyncLogCompanion({
|
||||
this.id = const Value.absent(),
|
||||
this.type = const Value.absent(),
|
||||
this.description = const Value.absent(),
|
||||
this.updatedAt = const Value.absent(),
|
||||
});
|
||||
|
||||
SyncLogCompanion.insert({
|
||||
this.id = const Value.absent(),
|
||||
required SyncLogTypeEnum type,
|
||||
this.description = const Value.absent(),
|
||||
this.updatedAt = const Value.absent(),
|
||||
}) : type = Value(type);
|
||||
|
||||
static Insertable<SyncLogData> custom({
|
||||
Expression<int>? id,
|
||||
Expression<int>? type,
|
||||
@@ -1540,8 +1533,10 @@ class SyncLogCompanion extends UpdateCompanion<SyncLogData> {
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('SyncLogCompanion(')
|
||||
..write('id: $id, ')..write('type: $type, ')..write(
|
||||
'description: $description, ')..write('updatedAt: $updatedAt')
|
||||
..write('id: $id, ')
|
||||
..write('type: $type, ')
|
||||
..write('description: $description, ')
|
||||
..write('updatedAt: $updatedAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
@@ -2905,7 +2900,6 @@ class $$SyncLogTableFilterComposer
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
|
||||
ColumnFilters<int> get id => $composableBuilder(
|
||||
column: $table.id,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
@@ -2937,7 +2931,6 @@ class $$SyncLogTableOrderingComposer
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
|
||||
ColumnOrderings<int> get id => $composableBuilder(
|
||||
column: $table.id,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
@@ -2968,7 +2961,6 @@ class $$SyncLogTableAnnotationComposer
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
|
||||
GeneratedColumn<int> get id =>
|
||||
$composableBuilder(column: $table.id, builder: (column) => column);
|
||||
|
||||
@@ -3069,7 +3061,6 @@ class $AppDatabaseManager {
|
||||
$$RecurringTransactionsTableTableManager(_db, _db.recurringTransactions);
|
||||
$$TransactionsTableTableManager get transactions =>
|
||||
$$TransactionsTableTableManager(_db, _db.transactions);
|
||||
|
||||
$$SyncLogTableTableManager get syncLog =>
|
||||
$$SyncLogTableTableManager(_db, _db.syncLog);
|
||||
}
|
||||
|
||||
16
lib/Tasks/background_init.dart
Normal file
16
lib/Tasks/background_init.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
import '../Controller/port_controller.dart';
|
||||
|
||||
/// Initialisiert benötigte Services in Background-Isolates
|
||||
Future<void> initBackground() async {
|
||||
final Logger logger = Logger()..d('Init Background for Native');
|
||||
|
||||
final RootIsolateToken? rootIsolateToken = await PortController()
|
||||
.getRootIsolateToken();
|
||||
if (rootIsolateToken != null) {
|
||||
logger.i('Initialising BackgroundIsolateBinaryMessenger...');
|
||||
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
|
||||
}
|
||||
}
|
||||
6
lib/Tasks/background_init_web.dart
Normal file
6
lib/Tasks/background_init_web.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
/// Initialisiert benötigte Services in Background-Isolates für Web
|
||||
Future<void> initBackground() async {
|
||||
Logger().d('Init Background for Web');
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
import '../Entities/drift_database.dart';
|
||||
import '../Entities/time_frame_enum.dart';
|
||||
@@ -13,12 +14,18 @@ class GenerateTransactionsTask extends Task {
|
||||
final RecurringTransactionRepository _recurringTransactionRepository =
|
||||
RecurringTransactionRepository();
|
||||
|
||||
final Logger _logger = Logger();
|
||||
|
||||
@override
|
||||
Future<bool> execute() async {
|
||||
_logger.i('Generating Transactions of recurring Transactions...');
|
||||
|
||||
final List<RecurringTransaction> recurringTransactions =
|
||||
await _recurringTransactionRepository.findBy();
|
||||
|
||||
for (final recurringTransaction in recurringTransactions) {
|
||||
_logger.i('Generating Transactions of $recurringTransaction...');
|
||||
|
||||
final List<Transaction> transactions = await _transactionRepository
|
||||
.findBy(
|
||||
recurringTransaction: recurringTransaction,
|
||||
@@ -43,6 +50,7 @@ class GenerateTransactionsTask extends Task {
|
||||
}
|
||||
|
||||
if (DateTime.now().compareTo(nextTransactionDate) <= 0) {
|
||||
// TODO: Nicht mit NOW, sondern Ende dieses Monats Vergleichen
|
||||
final TransactionsCompanion transaction = TransactionsCompanion(
|
||||
name: Value(recurringTransaction.name),
|
||||
date: Value(nextTransactionDate),
|
||||
@@ -52,10 +60,15 @@ class GenerateTransactionsTask extends Task {
|
||||
recurringTransactionId: Value(recurringTransaction.id),
|
||||
);
|
||||
|
||||
_logger.i(
|
||||
'Adding transaction ${transaction.name} on ${transaction.date}',
|
||||
);
|
||||
|
||||
await _transactionRepository.add(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.i('Generating transactions completed.');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
38
lib/Tasks/workers.dart
Normal file
38
lib/Tasks/workers.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:isolate_manager/isolate_manager.dart';
|
||||
|
||||
import 'background_init_web.dart' if (dart.library.io) 'background_init.dart';
|
||||
import 'generate_transactions_task.dart';
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
@isolateManagerWorker
|
||||
/// Führt eine Hintergrundtask lokal aus
|
||||
Future<void> runTask(final Map<String, dynamic> params) async {
|
||||
final String taskName = params['taskName'];
|
||||
final int initialDelayMinutes = params['initialDelayMinutes'];
|
||||
final int frequencyMinutes = params['frequencyMinutes'];
|
||||
|
||||
await Future.delayed(Duration(minutes: initialDelayMinutes));
|
||||
|
||||
await executeTask(taskName, null);
|
||||
|
||||
await Future.delayed(Duration(minutes: frequencyMinutes));
|
||||
|
||||
unawaited(runTask(params));
|
||||
}
|
||||
|
||||
/// Funktion um Hintergrundaufgaben auszuführen
|
||||
Future<bool> executeTask(
|
||||
final String taskName,
|
||||
final Map<String, dynamic>? inputData,
|
||||
) async {
|
||||
await initBackground();
|
||||
|
||||
switch (taskName) {
|
||||
case 'generate_transactions':
|
||||
return GenerateTransactionsTask().execute();
|
||||
}
|
||||
|
||||
return Future.value(true);
|
||||
}
|
||||
9
lib/Tasks/workmanager_workers.dart
Normal file
9
lib/Tasks/workmanager_workers.dart
Normal file
@@ -0,0 +1,9 @@
|
||||
import 'package:workmanager/workmanager.dart';
|
||||
|
||||
import 'workers.dart';
|
||||
|
||||
/// Die Funktion wird von Hintergrundtasks ausgerufen, um diese auszuführen
|
||||
@pragma('vm:entry-point')
|
||||
void callbackDispatcher() {
|
||||
Workmanager().executeTask(executeTask);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:routemaster/routemaster.dart';
|
||||
|
||||
import 'Controller/background_task_controller.dart';
|
||||
import 'Controller/port_controller.dart';
|
||||
import 'Services/navigation_service.dart';
|
||||
import 'Services/router_service.dart';
|
||||
import 'Services/theme_service.dart';
|
||||
@@ -10,6 +11,7 @@ import 'Services/theme_service.dart';
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
PortController();
|
||||
BackgroundTaskController();
|
||||
|
||||
runApp(
|
||||
|
||||
15465
web/runTask.js
Normal file
15465
web/runTask.js
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user