So I wanted to use Hive for storing notes in a Calendar app but I am stuggling so much with implementing Hive and a ChangeNotifierProvider together . If anyone has an idea on what to do I would like to see it.
Here is my code until now :
#HiveType(typeId: 0)
class EventsBox extends HiveObject {
EventsBox({required this.date, required this.eventsList});
#HiveField(0)
DateTime date;
#HiveField(1)
List<CleanCalendarEvent> eventsList;
}
And here is the FutureProvider that is needed :
final hiveProvider = FutureProvider<HiveDB>((_) => HiveDB.create());
class HiveDB {
var _events;
HiveDB._create() {}
static Future<HiveDB> create() async {
final component = HiveDB._create();
await component._init();
return component;
}
_init() async {
Hive.registerAdapter(EventsBoxAdapter());
this._events = await Hive.openBox<EventsBox>('events');
}
storeEvent(EventsBox eventsMap) {
this._events.put('events', eventsMap);
}
EventsBox getEvents() {
return this._events.get('events');
}
}
I want to use ChangeNotifierProvider and not FutureProvider
The goal of the provider framework is to inject dependencies into your app without recreating them all the time. In your example, you have your HiveDB class which is what you want to inject into your app for various other widgets to use it.
IMO, the general approach of "providing" dependencies is:
Create dependency instances outside the app (Typically in the main() function)
Create a provider which injects this dependency object
Wrap your MaterialApp with the provider
Supply your dependency object into the provider wrapper
Use the Provider<DependencyClass>.of(context) to access your dependency wherever you need it in your app.
Let's see how this applies to your code:
1. Setup your Hive models and HiveDB
lib/models/event_box.dart
#HiveType(typeId: 0)
class EventsBox extends HiveObject {
#HiveField(0)
DateTime date;
#HiveField(1)
List<CleanCalendarEvent> eventsList;
EventsBox({
required this.date,
required this.eventsList,
});
}
lib/models/hive_db.dart
class HiveDB {
var _events;
HiveDB._create() {}
static Future<HiveDB> create() async {
final component = HiveDB._create();
await component._init();
return component;
}
_init() async {
Hive.registerAdapter(EventsBoxAdapter());
this._events = await Hive.openBox<EventsBox>('events');
}
storeEvent(EventsBox eventsMap) {
this._events.put('events', eventsMap);
}
EventsBox getEvents() {
return this._events.get('events');
}
}
In this case, you want to "provide" (or inject) an instance of HiveDB to your app.
2. Create a provider for your HiveDB instance
lib/models/hive_db_provider.dart
class HiveDbProvider extends StatelessWidget {
final HiveDB db;
final Widget child;
const HiveDbProvider({
Key? key,
required this.db,
required this.child,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<HiveDB>.value(
value: db,
child: Consumer<AppCache>(
builder: (context, db, _) {
return child;
},
),
);
}
}
3. Create HiveDB instance in main()
lib/main.dart
void main() async {
final HiveDB db = await HiveDB.create();
runApp(
MyApp(db: db)
);
}
...
4. Inject the HiveDB instance using the provider
lib/main.dart
...
class MyApp extends StatelessWidget {
final HiveDB db;
const PayBuddy({
Key? key,
required this.db,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return HiveDbProvider(
db: db,
child: CacheProvider(
cache: cache,
child: MaterialApp(
...
),
),
);
}
}
5. Access "provided" instance using Provider anywhere in your app
lib/pages/some_screen.dart
import "package:provider/provider.dart";
...
class SomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Access HiveDB instance
final hiveDb = Provider<HiveDB>.of(context);
...
}
}
Conclusion
The sample uses the Provider package which simplifies using provider usage in Flutter.
Related
I'm building my first app, and for state management I'm using ValueChangeNotifier and Provider with the state pattern. But when I start my app, I get the following error:
Exception has occurred.
FlutterError (setState() or markNeedsBuild() called during build.
This _InheritedProviderScope<EvaluationStore?> widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was:
_InheritedProviderScope<EvaluationStore?>
The widget which was currently being built when the offending call was made was:
Builder)
I don't know how to show my problem without showing my project's classes, so I apologize if this gets too long.
I created a model class.
class EvaluationModel {
final String token;
final DateTime creation;
final String technicians;
final String customer;
final String responsible;
final String compressor;
final int horimeter;
final int oilType;
final int oil;
final int oilFilter;
final int airFilter;
final int separatorFilter;
final int revitalize;
final int revitalization;
final String? technicalAdvice;
final bool uploaded;
// continues with the basic methods of a data class...
}
So I created a service class that is responsible for the EvaluationModel methods, where I created a method to fill my list with data coming from a MySQL database.
class EvaluationService {
Future<List<EvaluationModel>> fetchEvaluations(
String creationStart,
String creationEnd,
String technicians,
String customer,
String compressor) async {
List<EvaluationModel> evaluations = <EvaluationModel>[];
EvaluationModel evaluation;
final MySqlConnection conn = await Database.getDbConnection();
final Results result = await conn.query(
await rootBundle.loadString('lib/assets/evaluation_select.sql'),
[creationStart, creationEnd, technicians, customer, compressor]);
await conn.close();
for (var row in result) {
evaluation = EvaluationModel(
token: row['token'],
creation: row['creation'],
technicians: row['technicians'],
customer: row['customer'],
responsible: row['responsible'],
compressor: row['compressor'],
horimeter: row['horimeter'],
oilType: row['oiltype'],
oil: row['oil'],
oilFilter: row['oilfilter'],
airFilter: row['airfilter'],
separatorFilter: row['separatorfilter'],
revitalize: row['revitalize'],
revitalization: row['revitalization'],
technicalAdvice: row['technicalAdvice'],
uploaded: true);
evaluations.add(evaluation);
}
return evaluations;
}
}
Then I created the EvaluationState and EvaluationStore class to manage the state of my page.
abstract class EvaluationState {}
class InitialEvaluationState extends EvaluationState {}
class LoadingEvaluationState extends EvaluationState {}
class SuccessEvaluationState extends EvaluationState {
final List<EvaluationModel> evaluations;
SuccessEvaluationState(this.evaluations);
}
class ErrorEvaluationState extends EvaluationState {
final String message;
ErrorEvaluationState(this.message);
}
class EvaluationStore extends ValueNotifier<EvaluationState> {
final EvaluationService service;
EvaluationStore(this.service) : super(InitialEvaluationState());
Future fetchEvaluations(String creationStart, String creationEnd,
String technicians, String customer, String compressor) async {
value = LoadingEvaluationState();
try {
final evaluations = await service.fetchEvaluations(
creationStart, creationEnd, technicians, customer, compressor);
value = SuccessEvaluationState(evaluations);
} catch (e) {
value = ErrorEvaluationState(e.toString());
}
}
}
So, to work with the Provider I did it like this in the MyApp class.
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider(create: (_) => EvaluationService()),
ChangeNotifierProvider(
create: (context) => EvaluationStore(context.read()))
],
child: MaterialApp(
title: 'Avaliação',
theme: ThemeData(
primarySwatch: Colors.deepOrange,
),
home: const EvaluationsPage(),
),
);
}
And finally, on the page I'm treating it like this:
class EvaluationsPage extends StatefulWidget {
const EvaluationsPage({Key? key}) : super(key: key);
#override
State<EvaluationsPage> createState() => _EvaluationsPageState();
}
class _EvaluationsPageState extends State<EvaluationsPage> {
#override
void initState() {
super.initState();
context
.read<EvaluationStore>()
.fetchEvaluations('0001-01-01', '9999-12-31', '%', '%', '%');
}
#override
Widget build(BuildContext context) {
final store = context.watch<EvaluationStore>();
final state = store.value;
Widget? child;
if (state is LoadingEvaluationState) {
child = const Center(child: CircularProgressIndicator());
}
if (state is ErrorEvaluationState) {
child = Center(child: Text(state.message));
}
if (state is SuccessEvaluationState) {
child = ListView.builder(
itemCount: state.evaluations.length,
itemBuilder: (context, index) {
return ListTile(title: Text(state.evaluations[index].customer));
});
}
return Scaffold(
appBar: AppBar(title: const Text('Avaliações')),
body: child ?? Container(),
);
}
}
Note: If I remove the line "value = LoadingEvaluationState();" from the Evaluation Store class, the app runs normally.
If anyone can help me, I can even make the project available.
I'm a beginner, I'm totally stuck, I don't know what to try.
the error occured because while execute the initState method, you call rebuild .
simple solution:
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){
context
.read<EvaluationStore>()
.fetchEvaluations('0001-01-01', '9999-12-31', '%', '%', '%');
}
});
I'm trying to use getX as flutter state management tool. GetX has its way handling language translation. One thing that I'm not sure is how could I initialise translation source from a remote server instead of hard code the translations. In such a way, I have the benefit of modifying translation without the need to release a new app.
Any suggest are welcome. Thanks.
I have DotNet WebAPI backend and I am sending translations like the following format:
[ApiController]
[Route("[controller]")]
public class TranslationsController : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetAll()
{
return Ok(new
{
en_US = new
{
hi = "Hi",
bye = "Bye"
},
bn_BD = new
{
hi = "ওহে",
bye = "বিদায়"
}
});
}
}
And my AppTranslations class:
class AppTranslations extends Translations {
final Map<String, String> en_US;
final Map<String, String> bn_BD;
AppTranslations({required this.en_US, required this.bn_BD});
static AppTranslations fromJson(dynamic json) {
return AppTranslations(
en_US: Map<String, String>.from(json["en_US"]),
bn_BD: Map<String, String>.from(json["bn_BD"]),
);
}
#override
Map<String, Map<String, String>> get keys => {
"en_US": en_US,
"bn_BD": bn_BD,
};
}
My TranslationProvider:
class TranslationProvider extends GetConnect {
Future<AppTranslations?> getTranslations() async {
final url = 'http://192.168.0.106:5000/translations';
final response = await get(url, decoder: AppTranslations.fromJson);
if (response.hasError) {
return Future.error(response.statusText!);
}
return response.body;
}
}
Then in my main function:
void main() async {
final translationProvider = TranslationProvider();
final translations = await translationProvider.getTranslations();
runApp(MyApp(translations: translations!));
}
And here's my MyApp:
class MyApp extends StatelessWidget {
final AppTranslations translations;
const MyApp({Key? key, required this.translations}) : super(key: key);
#override
Widget build(BuildContext context) {
return GetMaterialApp(
translations: translations,
locale: Locale("en_US"),
home: MyHomePage(),
);
}
}
And you are done! Now you can update your translations in the API and the update will reflect on the app.
So, after month worth of digging around, apparently Getx had its own method to add translations on the fly.
apiProvider.get('/api/translations').then((value) {
Get.clearTranslations();
Get.addTranslations(value.data['languages']);
}
virtually, you can call this anywhere you want!
I'm trying to figure out the BLoC library, but it gives me headaches.
I'm trying to fetch hotel names from an API. I have a model and a service responsible for contacting the API and fetching the data. However, I don't know how to connect it to the BLoC library.
Once my app starts, I want BLoC to fetch the data from the API and then show it in the app.
Here's my code:
hotel_model.dart
class Hotels {
final List<Hotel> hotels;
Hotels({this.hotels});
factory Hotels.fromJson(Map<String, dynamic> json) {
return Hotels(
hotels: List<Hotel>.from(
json['hotels'].map(
(x) => Hotel.fromJson(x),
),
),
);
}
}
class Hotel {
final String hotelName;
Hotel({this.hotelName});
factory Hotel.fromJson(Map<String, dynamic> json) {
return Hotel(
hotelName: json['name'],
);
}
}
hotel_service.dart
import 'package:http/http.dart' as http;
abstract class DownloadService {
Future<http.Response> fetchHotels();
}
class HotelService extends DownloadService {
#override
Future<http.Response> fetchHotels() {
final Uri uri = Uri.https('services.lastminute.com', 'mobile/stubs/hotels');
return http.get(uri);
}
}
And here's what I did wit the BLoC lib.
hotel_event.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelEvent {}
class OnAppStartEvent extends HotelEvent {}
hotel_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:hotels/models/hotel/hotel_model.dart';
import 'package:hotels/services/hotel/hotel_service.dart';
import 'package:meta/meta.dart';
part 'hotel_event.dart';
part 'hotel_state.dart';
class HotelBloc extends Bloc<HotelEvent, HotelState> {
HotelBloc() : super(HotelFinal());
final HotelService hotelService = HotelService();
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
final response = hotelService.fetchHotels();
yield
}
}
}
hotel_state.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotel hotel;
HotelFinal(this.hotel);
Hotel getHotel() {
return hotel;
}
}
First of all add await to this line in your bloc
final response = await hotelService.fetchHotels();
return List<Hotel> from your fetchHotels function
you must have stateful class for your screen and in the initState
you can create your bloc object and call .add method on it
in your build method wrap your widget with BlocBuilder and on builder callback check your bloc state, if the state is HotelFinal return your ui with list of hotels in your state object.
It'll be useful to add another state for your HotelState for when your bloc is fetching the data, and even for when there's an error. e.g;
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotel hotel;
HotelFinal(this.hotel);
Hotel getHotel() {
return hotel;
}
}
class HotelLoading extends HotelState {
HotelLoading();
}
class HotelError extends HotelState {
final String error;
HotelError(this.error);
}
You would want to change your mapEventToState to something like this:
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
yield HotelLoading();
try {
final response = await hotelService.fetchHotels();
// It seems like your service doesn't return an hotel directly, so you'll have to deal with this as it is not part of the question.
final hotel = getYourHotelHereWithTheResponse;
yield HotelFinal(hotel);
} catch (e) {
yield HotelError('something went wrong getting the hotel info');
}
}
}
Lastly, add a widget to your widget tree that adds FetchEvent to your bloc and add a BlocBuilder to react to the change of states. Note that this is very flexible and can be done in many ways, but it is out of the scope of your very broad question, I'm just showing you how to use the library at a minimal:
class MyStatefulWidget extends StatefulWidget {
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
HotelBloc hotelBloc;
#override
void initState() {
hotelBloc = HotelBloc..add(FetchEvent());
super.initState();
}
#override
void dispose() {
hotelBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder(builder: (context, state) {
if(state is HotelLoading) {
// return a widget to deal with loading
}
if(state is HotelFinal) {
// return a widget to deal with success
}
if(state is HotelError) {
// return a widget to deal with error
}
});
}
}
I am so confused about state management.
Below is I pass down data through widgets.
List<AppUser> userList = List<AppUser>();
List<List<MessageType>> messageLists = new List<List<MessageType>>();
#override
void initState() {
super.initState();
loadUsers();
}
Future<void> loadUsers() async {
userList.clear();
userList.addAll(await AppUser.getRelatedUsers(customer.customerID));
defaultUser = await AppUser.getDefaultUser(customer.customerID);
if (defaultUser != null && !await defaultUser.hideUserTab()) {
userList.add(defaultUser);
}
await loadMessageList();
}
Then I pass the userList and messageList to another stateful widget. But what if I want to have those data through the whole app using inherited widget or provider or bloc.
MessageTypePage(
messageTypeList: messageLists[tabIndex],
currentUser: userList[tabIndex],
);
How can I possible to get the data from db and store them in inherited widget then using those data? I am so confused.
class StateContainer extends StatefulWidget {
final Widget child;
final List<AppUser> userList;
final List<Message> messageList;
StateContainer({#required this.child, this.userList, this.messageList});
static StateContainerState of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<_InheritedStateContainer>().data;
}
#override
StateContainerState createState() => new StateContainerState();
}
class StateContainerState extends State<StateContainer> {
List<AppUser> userList = List<AppUser>();
List<List<MessageType>> messageLists = new List<List<MessageType>>();
#override
Widget build(BuildContext context) {
return _InheritedStateContainer(
data: this,
child: widget.child,
);
}
}
class _InheritedStateContainer extends InheritedWidget {
final StateContainerState data;
_InheritedStateContainer({Key key, #required this.data, #required Widget child}) : super(key: key, child: child);
#override
bool updateShouldNotify(_InheritedStateContainer oldWidget) {
return true;
}
}
In my opinion, the best approach is to use Provider or Bloc. There is a flutter codelab that uses Provider to do something very similar to what you are doing. It stores a list of items (in your case that would be Users) that can be used throughout the app. It also shows you how to manipulate the list in various ways.
The codelab is here. I think it would help you out.
I'm having trouble accessing a services object when initializing a stateful widget. The problem comes from the context object not being available in initState.
I'm using InheritedWidget to inject a services object in my main.dart file like so
void main() async {
final sqflite.Database database = await _openDatabase('db.sqlite3');
runApp(
Services(
database: database,
child: MyApp(),
),
);
}
The Services object is quite straightforward. It will have more than just the database as a member. The idea is that the widgets don't need to know if a local database, local cache, or remote server is being accessed.
class Services extends InheritedWidget {
final Database database;
const Services({
Key key,
#required Widget child,
#required this.database,
}) : assert(child != null),
assert(database != null),
super(key: key, child: child);
Future<List<models.Animal>> readAnimals() async {
return db.readAnimals(database: this.database);
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) {
return false;
}
static Services of(BuildContext context) {
return context.inheritFromWidgetOfExactType(Services) as Services;
}
}
The trouble comes in my _HomePageState state when I want to load all the animals from the database. I need to access the Services object. I cannot access the Services object in initState so I am using didChangeDependencies. A problem comes when the home page is removed from the stack. It seems didChangeDependences is called and the access to the context object is illegal. So I created an _initialized flag that I can use in didChangeDependencies to ensure I only load the animals the first time. This seems very inelegant. Is there a better way?
class _HomePageState extends State<HomePage> {
bool _initialized = false;
bool _loading = false;
List<Animal> _animals;
#override
Widget build(final BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Strings.of(this.context).appName),
),
body: _HomeBody(
loading: this._loading,
animals: this._animals,
),
);
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (!this._initialized) {
this._initialized = true;
this._loadAnimals();
}
}
void _loadAnimals() async {
this.setState(() {
this._loading = true;
this._animals = null;
});
final List<Animal> animals = await Services.of(this.context).readAnimals();
this.setState(() {
this._loading = false;
this._animals = animals;
});
}
}
For that case you could use addPostFrameCallback of your WidgetsBinding instance to execute some code after your widget was built.
_onLayoutDone(_) {
this._loadAnimals();
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
super.initState();
}