isolator 0.0.5 isolator: ^0.0.5 copied to clipboard
Isolator is a package, which offer to you a simple way for creating two-component states with isolated part and frontend part of any kind - BLoC, MobX, ChangeNotifier and others
isolator #
Isolator is a package, which offer to you a simple way for creating two-component states with isolated part and frontend part of any kind (BLoC, MobX, ChangeNotifier and many others).
This package is a trying to proof of concept, when you take out heavy business logic to isolates for achievement a fully cleared from any lugs application. With this package you can easy create a so-called "backend" - class with your logic and second class, which uses a special mixin - a state of any kind - BLoC / MobX / ChangeNotifier (as in an example).
Example (with null-safety now) #
import 'package:flutter/cupertino.dart';
import 'package:isolator/isolator.dart';
enum TestEvent { intSync, intAsync, intAsyncWithReturn, chunks, observer, errorOnBackend, invalidType, afterCreation }
const int VALUE_AFTER_CREATION = 11;
const int ASYNC_INT = 10;
const int SYNC_INT = 12;
/// Example of state with ChangeNotifier
class ChangeNotifierFrontend with Frontend<TestEvent>, ChangeNotifier {
@override
Map<TestEvent, Function> get tasks => {};
}
/// Frontend - anything else, what you want to use a state of your app
class FrontendTest with Frontend<TestEvent> {
int asyncIntFromBackend = 0;
int syncIntFromBackend = 0;
int valueAfterCreation = 0;
bool isErrorHandled = false;
List<int> intChunks = [];
/// You can get any value (after calculating it on the backend) in synchronous style
/// When you using [runBackendMethod] function - you call your Backend's method, which
/// matches the passed event id, and get back value in that place
///
/// It most simplest way to use Isolator
Future<int> getIntFromBackendSync() async {
final int intFromBackend = await runBackendMethod(TestEvent.intSync);
syncIntFromBackend = intFromBackend;
return syncIntFromBackend;
}
/// Also, you can use asynchronous style
/// It is the way, when you send some params (with event id) to Backend
/// Then, Backend handle it, and after that you handle Backend response via Frontend task
void loadIntFromBackend() {
send(TestEvent.intAsync);
}
/// It is a task for handle Backend response
/// for event id [TestEvent.intAsync]
void _setIntFromBackend(int intFromBackend) {
this.asyncIntFromBackend = intFromBackend;
}
/// You can return value from backend with simple "return" keyword
/// without using [send] method of your Backend
/// To see that - open [_returnIntBack] method of [BackendTest]
void loadIntFromBackendWithReturn() {
send(TestEvent.intAsyncWithReturn);
}
/// When you want get a large amount of data from the Backend
/// You can use [sendChunks] method of the Backend
/// For example - see method [_returnChunks] of [BackendTest]
void loadChunks() {
send(TestEvent.chunks);
}
/// Task for handle [sendChunks] event must take a [List] of data
void _setIntChunks(List<int> intChunks) {
this.intChunks.clear();
this.intChunks.addAll(intChunks);
}
/// Before using Backend with Frontend, you should init your Backend
/// To do this - simple use [initBackend] method of your Frontend
Future<void> init(int id) async {
await initBackend<int>(_backendFabric, data: VALUE_AFTER_CREATION, id: '$id');
}
/// If you want to destroy Backend - use [killBackend] method
/// It can be useful, if your state lifetime is shorter than lifetime of app
void dispose() {
killBackend();
}
/// Hook, which calls on every error
/// Which throws in the Backend
@override
Future<void> onError(dynamic error) async {
await super.onError(error);
}
/// Hook, which calls on every event from the Backend
@override
void onBackendResponse() {
super.onBackendResponse();
}
/// This method need for test cases
void invalidType() {
send(TestEvent.invalidType);
}
/// This method need for test cases
void runError() {
send(TestEvent.errorOnBackend);
}
/// This method need for test cases
void _taskWithInvalidType(String intFromBackend) {
// WAITING FOR ERROR
}
/// This method need for test cases
void _handleError(dynamic error) {
isErrorHandled = true;
}
/// This method need for test cases
void _setValueAfterCreation(int valueAfterCreation) {
this.valueAfterCreation = valueAfterCreation;
}
/// [tasks] - Map of methods, which calls on events with
/// matched ids from Backend
@override
Map<TestEvent, Function> get tasks => {
TestEvent.intAsync: _setIntFromBackend,
TestEvent.intAsyncWithReturn: _setIntFromBackend,
TestEvent.chunks: _setIntChunks,
TestEvent.invalidType: _taskWithInvalidType,
TestEvent.afterCreation: _setValueAfterCreation,
};
/// [errorsHandlers] - Map of methods, which calls, if error
/// was thrown in the Backend, while Backend handle operation
/// with matched event id
@override
Map<TestEvent, ErrorHandler> get errorsHandlers => {
TestEvent.errorOnBackend: _handleError,
};
}
/// Backend - class, which will handle your logic in separate isolate
class BackendTest extends Backend<TestEvent> {
BackendTest(BackendArgument<int> argument) : super(argument) {
_sendValueAfterCreation(argument.data!);
}
/// You can send value to the Frontend with [send] method
/// Also, you can use this method any times in all of you Backend methods
void _sendIntBack() {
send(TestEvent.intAsync, ASYNC_INT);
send(TestEvent.observer);
}
/// Or you can simply return the value and your Frontend will receive a message with exact event id
/// For example - there Frontend will receive event [TestEvent.intAsyncWithReturn] with value [ASYNC_INT]
int _returnIntBack() {
return ASYNC_INT;
}
void _sendValueAfterCreation(int value) {
send(TestEvent.afterCreation, value);
}
int _returnSyncInt() {
return SYNC_INT;
}
int _returnValue() {
return SYNC_INT;
}
void _throwError() {
throw Exception('Manual error');
}
/// Example of using [sendChunks] method for sending a large amount of data
/// from the Backend to the Frontend without junks of your interface
void _returnChunks() {
final List<int> chunks = [];
for (int i = 0; i < 10000; i++) {
chunks.add(i);
}
/// You can control delay between chunks and amount of items in one chunk
/// to achieve a lowest time for the data transfering and doesn't have any junks
sendChunks(TestEvent.chunks, chunks, delay: const Duration(milliseconds: 3), itemsPerChunk: 1000);
}
/// [operations] - Map of methods, which similar to [tasks] of Frontend
/// every operation will handle events from the Frontend with matched event id
@override
Map<TestEvent, Function> get operations => {
TestEvent.intAsync: _sendIntBack,
TestEvent.intAsyncWithReturn: _returnIntBack,
TestEvent.intSync: _returnSyncInt,
TestEvent.invalidType: _returnValue,
TestEvent.errorOnBackend: _throwError,
TestEvent.chunks: _returnChunks,
};
}
class AnotherFrontend {
AnotherFrontend(this.frontendTest);
final FrontendTest frontendTest;
int intFromFrontendTest = 0;
void subscriptionForFrontendTest() {
this.intFromFrontendTest = frontendTest.asyncIntFromBackend;
}
/// You can subscribe on every available (your) event of your Frontend
void subscribe() {
frontendTest.onEvent(TestEvent.observer, subscriptionForFrontendTest);
}
}
void _backendFabric(BackendArgument<int> argument) {
BackendTest(argument);
}
Restrictions #
- Backend classes can't use a native layer (method-channel)
- For one backend - one isolate (too many isolates take much time for initialization, for example: ~6000ms for 30 isolates at emulator in dev mode)