hive
hive copied to clipboard
Hive on another isolation
Steps to Reproduce I am implementing isolation in my test project.
I have these 5 buttons in my UI
- Display button - to display the value of hive box
- Open - to open the box
- Close - to close the box
- Download via isolation - just a mimic, what logic here is that we will add data on hive box through isolation
- Floating button - just a simulation to stop the download progress.
Steps:
- We need to press the open so it will open the hive box
- We need to press the 'Download via Isolation'
- Now , press the Display button to display the data. However the problem will occur here. The problem is that as we did in second second step, we add data in hive box via isolation, now then when we display the data the result is null or did not fetch the data we have been added in isolation. So I try to close the box first by press the Close button, and then open it the box again. Then try to display the data, now it is successfully show the data. But I think this is a bug right? it supposed to be that the box in main isolation should be updated when there is changes even if we add some data via second isolation. Overall, it seems that we need to close first the hive box just to refresh the hive box, then open it again.
Code sample
import 'dart:async';
import 'dart:developer';
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hive_flutter/adapters.dart';
import 'package:path_provider/path_provider.dart';
import 'package:rive/rive.dart';
import 'package:username_gen/username_gen.dart';
class CloseToken {
bool _isClose;
CloseToken() : _isClose = false;
bool get isCLose => _isClose;
//setter
set close(bool value) {
_isClose = value;
}
//copy with
CloseToken copyWith({
bool? isClose,
}) {
return CloseToken().._isClose = isClose ?? _isClose;
}
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _isDownload = false;
double _progress = 0;
late final StreamController<CloseToken> _closeTokenStream;
static late SendPort downloadIsolateSendPort;
late final StreamSubscription<CloseToken> _closeTokenStreamSubscription;
@override
void initState() {
super.initState();
_closeTokenStream = StreamController<CloseToken>.broadcast();
_closeTokenStreamSubscription =
_closeTokenStream.stream.asBroadcastStream().listen((event) {
downloadIsolateSendPort.send(event);
});
}
@override
void dispose() {
super.dispose();
_closeTokenStreamSubscription.cancel();
_closeTokenStream.close();
}
/// create a Isolate in dart should be follow like this
/// Start
/// 1. create a ReceivePort
/// 2. create a SendPort
/// 3. create a Isolate
/// 4. send the SendPort to the Isolate
/// 5. listen the ReceivePort
/// 6. send the message to the Isolate
/// End
void downloadPort() async {
// root Isolation token
final rootIsolationToken = RootIsolateToken.instance;
// Hive.close();
if (_isDownload) return;
_isDownload = true;
final receivePort = ReceivePort();
final isolate = await Isolate.spawn<List<Object>>(
downloadIsolate, [receivePort.sendPort, rootIsolationToken!]);
receivePort.listen((message) {
if (message is SendPort) {
downloadIsolateSendPort = message;
} else if (message is double) {
log('Progress: $message');
setState(() {
_progress = message / 10;
});
} else if (message is String) {
log('Close: $message');
_isDownload = false;
_progress = 0;
} else if ((message is bool)) {
log('Is done?: $message');
_isDownload = false;
_progress = 0;
isolate.kill();
}
});
}
static void downloadIsolate(List<Object> args) async {
final sendPort = args[0] as SendPort;
CloseToken closeToken = CloseToken();
final isolationToken = args[1] as RootIsolateToken;
// Register the background isolate with the root isolate.
BackgroundIsolateBinaryMessenger.ensureInitialized(isolationToken);
final appDorDir = await getApplicationDocumentsDirectory();
log('appDorDir: ${appDorDir.path}');
Hive.init(appDorDir.path);
final box = await Hive.openBox<String>('person');
if (box.isNotEmpty) {
await box.clear();
}
await box.add(UsernameGen().generate());
log(' (Download Isolation) box: ${box.values.toList().toString()}');
/// log the Isolation current
log(' (Download Isolation) Isolate.current: ${Isolate.current.debugName}');
final ReceivePort receivePort = ReceivePort();
sendPort.send(receivePort.sendPort);
receivePort.listen((message) {
if (message is CloseToken) {
closeToken = message;
}
});
double count = 0;
Timer.periodic(const Duration(seconds: 3), (timer) {
count++;
if (!closeToken._isClose && count <= 10) {
sendPort.send(count);
} else {
timer.cancel();
if (closeToken.isCLose) {
sendPort.send('Since the user closed the download');
} else {
sendPort.send(true);
}
Hive.close();
}
});
// exit
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: downloadPort,
child: const Text('Download via Isolation')),
const SizedBox(
height: 10,
),
SizedBox(
height: 10,
width: size.width * 0.8,
child: LinearProgressIndicator(
value: _progress,
),
),
const SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
if (!Hive.isBoxOpen('person')) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please open the box first')));
return;
}
final box = await Hive.openBox<String>('person');
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('name: ${box.values.toList()}')));
}
},
child: const Text('Display')),
const SizedBox(
width: 10,
),
ElevatedButton(
onPressed: () async {
await Hive.openBox<String>('person');
log('openBox');
},
child: const Text('Open')),
const SizedBox(
width: 10,
),
ElevatedButton(
onPressed: () async {
await Hive.close();
log('close');
},
child: const Text('Close')),
],
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_closeTokenStream.add(CloseToken()..close = true);
},
child: const Icon(Icons.close),
));
}
}
Version
- Platform: Android
- Flutter version: 3.7.3
- Hive version: (I used the hive_flutter so the version of my hive_flutter is 1.1.0)
I think we need a refresh method in the Box. so then it does need to close then open the box.
@moshOntong-IT I have the same issue. you got any solution ?
Isolate support has been pre-released for hive_ce!
Get the pre-releases here:
hive_ce hive_ce_flutter hive_ce_generator
Please try out IsolatedHive in your projects and report any issues you may have. I want to make sure all potential issues are ironed out before I make a real release.