Unhandled Exception: LateInitializationError: Field 'Db' has not been initialized - flutter

i am using this DbProvider class in many other files. it throws error:
[ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception:
LateInitializationError: Field 'Db' has not been initialized.
E/flutter (12867): #0 DbProvider.Db (package:sample/src/Db.dart)
E/flutter (12867): #1 Bloc.fetchInfoWithNameOrder
(package:sample/src/bloc.dart:31:24)
E/flutter (12867): #2 NameTiles.build.
(package:sample/src/NameTiles.dart:20:16)
E/flutter (12867): #3 StreamBuilder.build
(package:flutter/src/widgets/async.dart:546:81)
This is DbProvider class
class DbProvider{
late Database Db;
DbProvider(){init();}
init()async{
print("sdsdfaf");
Directory dir = await getApplicationDocumentsDirectory();
final path = join(dir.path, 'AppDb.db');
print(dir.path);
Db = await openDatabase(
path,
version: 1,
onCreate: (Database newDb, int version){
newDb.execute(
"""
create table Names(
id integer primary key,
name text,
Salary integer,
total integer
);
create table Att(
id integer primary key,
name text,
);
"""
);
}
);
print('\n\n\ndb init\n\n\n');
}
}
This is bloc.dart file
class Bloc{
final db = DbProvider();
final _text_field_subject = BehaviorSubject();
final _tile = BehaviorSubject();
Function(String?) get addToTextSubject => _text_field_subject.sink.add;
Stream get text_field_subject => _text_field_subject.stream;
get text_field_subject_value => _text_field_subject.value;
Function get addToTile => _tile.sink.add;
Stream get tile => _tile.stream;
fetchInfo(String arg) async{
print('\nfetchInfo\n');
var ans = await db.Db.query(
"Names",
where: "name = ?",
whereArgs: [arg],
);
print(ans);
addToTile(ans);
}
fetchInfoWithNameOrder()async{
print('\nfetchinfowithorder\n');
var ans = await db.Db.query(
"Names",
orderBy: "name asc",
);
print(ans);
addToTile(ans);
}
}
and here is the NameTiles class with builder:
class NameTiles extends StatelessWidget{
#override
Widget build(BuildContext context) {
print('\n\n\n1\n\n\n');
final db = DbProvider();
print('\n\n\n3\n\n\n');
final bloc = StreamProvider.of(context);
return StreamBuilder(
stream: bloc.tile,
builder: (context, AsyncSnapshot? snapshot){
print('\n\n\nre\n\n\n');
if(snapshot == null || !snapshot.hasData){
print('\n\n\nStarting\n\n\n');
bloc.fetchInfoWithNameOrder();
return Text('loading');
}
print('\n\n\ntween\n\n\n');
return Text('llll');
},
);
}
Widget tiles(info){
return Container(
height: 40,
padding: EdgeInsets.all(5),
clipBehavior: Clip.none,
child: Row(children: [
Text('${info.name}'),
Text('${info.total}'),
],),
);
}
}

You call init in your constructor, but you never wait for it to finish.
So your constructor runs, it trigers the init method, and while that runs, since it's async and has no await to wait for it, your program continues, finally coming to a part where your variable is needed, but not initialized yet, since init is still running.
Some tips:
crank up warnings to a maximum, maybe using a package like pedantic
actually provide return types for your async methods
move your construction of DbProvider out of your build method
instead of having a constructor, create a factory method and make it async, remember to provide a proper return type and call it with an await.
You may want to read up on What is a Future and how do I use it?

Related

Use ObjectBox / Instantiate store and box with Flutter 2.5 and Riverpod

I am using ObjectBox on Flutter 2.5 with Riverpod. The goal is to allow fast caching of a lot of data from an API.
If there is a difference between remote data and that in the box, then we update. The person who no longer has the internet still has good data.
Has anyone managed to instantiate and best use Objectbox in flutter clean architecture with riverpod ? I found several relevant questions about it here and here. but I can't seem to get anything satisfactory at the moment ...
If you have any suggestions for doing this.
I took into account the update that Objectbox made on their example of store creation.
The errors are always the same, either the _box or store is not instantiated or else, get null. I would update my post as I research.
[ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: LateInitializationError: Field '_box#59089156' has not been initialized.
[ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: LateInitializationError: Field 'store' has not been initialized.
------EDIT 21 oct.2021-----
Ok, thanks to the help and comments from my colleagues. I update my code to match. No more store or box instantiation problems.
My code
abstract class LocalDataSourceRepository<T> {
Future<Coin?> getCoinById(dynamic id);
Future<void> addCoin(T object);
}
The remote / local management is in this implementation, with getRemoteDataCoin which retrieves a full currency from the API and transforms it into an entity
class CoinRepositoryImpl extends LocalDataSourceRepository {
final CoinRemoteApi _remoteApi;
final ObjectBoxDatabase _objectBoxDatabase;
CoinRepositoryImpl(
this._remoteApi,
this._objectBoxDatabase,
);
#override
Future<Coin?> getCoinById(id) async {
final _box = _objectBoxDatabase.store.box<Coin>();
final Query<Coin> query = (_boxCoin.query(Coin_.coinId.equals(id))).build();
if (query.findUnique() == null) {
final coin = await getRemoteDataCoin(id);
addCoin(coin);
return query.findUnique();
} else {
return query.findUnique();
}
}
#override
Future<void> addCoin(object) async {
final _box = _objectBoxDatabase.store.box<Coin>();
_box.put(object);
}
Future<Coin> getRemoteDataCoin(selectedCoin) async {
return _remoteApi.getCoinById(
selectedCoin,
const CoinRequest(
localization: 'false',
tickers: false,
marketData: false,
communityData: true,
developerData: false,
sparkline: false
)
).then(
(value) => value.toEntity(),
);
}
}
class ObjectBoxDatabase {
late final Store store;
late final Box<Coin> _box;
ObjectBoxDatabase._create(this.store) {
_box = Box<Coin>(store);
}
static Future<ObjectBoxDatabase> create() async {
final store = await openStore();
return ObjectBoxDatabase._create(store);
}
}
My Riverpod providers.
final localCoinDatabaseProvider = Provider<ObjectBoxDatabase>((ref) => throw UnimplementedError());
final remoteCoinApiProvider = Provider<CoinRemoteApi>((ref) => CoinRemoteApi());
final coinRepositoryProvider = Provider<LocalDataSourceRepository>((ref) => CoinRepositoryImpl(
ref.read(remoteCoinApiProvider),
ref.read(localCoinDatabaseProvider)
));
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final objectBox = await ObjectBoxDatabase.create();
runApp(
ProviderScope(
overrides: [
localCoinDatabaseProvider.overrideWithValue(objectBox)
],
child: const MyApp()
)
);
}
Since you get the ObjectBoxDatabase using the .create method which is a future, you can either use a FutureProvider or overrides
FutureProvider:
final localCoinDatabaseProvider = FutureProvider<ObjectBoxDatabase>((ref) => ObjectBoxDatabase.create());
https://pub.dev/documentation/riverpod/latest/riverpod/FutureProvider-class.html
overrides:
final localCoinDatabaseProvider = Provider<ObjectBoxDatabase>((ref) => throw UnimplementedError());
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final objectBox = await ObjectBoxDatabase.create();
runApp(
const ProviderScope(
overrides: [localCoinDatabaseProvider.overrideWithValue(objectBox)]
child: MyApp()
)
);
}

Flutter | Riverpod & Dart Unhandled Exception: setState() or markNeedsBuild() called during build

Context
I have this AppUser class:
#immutable
class AppUser {
const AppUser({
this.displayName,
this.email,
required this.emailVerified,
this.phoneNumber,
this.photoURL,
required this.uid,
});
AppUser.fromFirebaseUser(User user)
: displayName = user.displayName,
email = user.email,
emailVerified = user.emailVerified,
phoneNumber = user.phoneNumber,
photoURL = user.photoURL,
uid = user.uid;
final String? displayName;
final String? email;
final bool emailVerified;
final String? phoneNumber;
final String? photoURL;
final String uid;
}
In order to manage and use the current user signed in, I have this AppUserController class:
class AppUserController extends StateNotifier<AppUser> {
AppUserController()
: super(
const AppUser(
emailVerified: false,
uid: '',
),
);
Stream<User?> get onAuthStateChanges =>
FirebaseAuth.instance.authStateChanges();
set setAppUser(AppUser appUser) {
state = appUser;
}
Future<void> signOut() async {
await FirebaseAuth.instance.signOut();
}
}
Then, I created 2 providers:
final appUserProvider =
StateNotifierProvider<AppUserController, AppUser>((ref) {
return AppUserController();
});
final appUserStreamProvider = StreamProvider<AppUser?>((ref) {
return ref
.read(appUserProvider.notifier)
.onAuthStateChanges
.map<AppUser?>((user) {
return user != null ? AppUser.fromFirebaseUser(user) : null;
});
});
I need to manage a user’s budgets list. Also, I have to synchronize this list with a Cloud Firestore database, so I created the BudgetsService class:
class BudgetsService {
BudgetsService({
required this.uid,
}) : budgetsRef = FirebaseFirestore.instance
.collection(FirestorePath.budgetsCollection(uid))
.withConverter<Budget>(
fromFirestore: (snapshot, _) => Budget.fromMap(snapshot.data()!),
toFirestore: (budget, _) => budget.toMap(),
);
String uid;
final CollectionReference<Budget> budgetsRef;
Future<void> addUpdate(Budget budget) async {
await budgetsRef.doc(documentPath(budget)).set(budget);
}
Future<void> delete(Budget budget) async {
await budgetsRef.doc(documentPath(budget)).delete();
}
String documentPath(Budget budget) => FirestorePath.budgetDoc(uid, budget);
Future<List<Budget>> getBudgets() async {
final list = await budgetsRef.get();
return list.docs.map((e) => e.data()).toList();
}
}
I use this class through budgetsServiceProvider provider:
final budgetsServiceProvider = Provider<BudgetsService>((ref) {
final AppUser appUser = ref.watch(appUserProvider);
final String uid = appUser.uid;
return BudgetsService(uid: uid);
});
I use BudgetsService class only to interact with the online database. For the rest, I manage the user’s budget list with BudgetsController class:
class BudgetsController extends StateNotifier<List<Budget>> {
BudgetsController() : super(<Budget>[]);
List<String> get names => state.map((b) => b.name).toList();
Future<void> addUpdate(Budget budget, BudgetsService budgetsService) async {
await budgetsService.addUpdate(budget);
if (budgetAlreadyExists(budget)) {
final int index = indexOf(budget);
final List<Budget> newState = [...state];
newState[index] = budget;
state = newState..sort();
} else {
state = [...state, budget]..sort();
}
}
bool budgetAlreadyExists(Budget budget) => names.contains(budget.name);
Future<void> delete(Budget budget, BudgetsService budgetsService) async {
await budgetsService.delete(budget);
final int index = indexOf(budget);
if (index != -1) {
final List<Budget> newState = [...state]
..removeAt(index)
..sort();
state = newState;
}
}
Future<void> retrieveBudgets(BudgetsService budgetsService) async {
state = await budgetsService.getBudgets();
}
int indexOf(Budget budget) => state.indexWhere((b) => b.name == budget.name);
}
I use this class through budgetsProvider provider:
final budgetsProvider =
StateNotifierProvider<BudgetsController, List<Budget>>((ref) {
return BudgetsController();
});
After the user is signed in, my SwitchScreen widget navigates to ConsoleScreen:
class SwitchScreen extends HookWidget {
const SwitchScreen({
Key? key,
}) : super(key: key);
static const route = '/switch';
#override
Widget build(BuildContext context) {
final appUserStream =
useProvider<AsyncValue<AppUser?>>(appUserStreamProvider);
final googleSignIn =
useProvider<GoogleSignInService>(googleSignInServiceProvider);
final appUserController =
useProvider<AppUserController>(appUserProvider.notifier);
return appUserStream.when(
data: (data) {
if (data != null) {
appUserController.setAppUser = data;
final budgetsService = useProvider(budgetsServiceProvider);
return const ConsoleScreen();
} else {
return SignInScreen(
onGooglePressed: googleSignIn.signInWithGoogle,
);
}
},
loading: () {
return const Scaffold(
body: Center(
child: LinearProgressIndicator(),
),
);
},
error: (error, stack) {
return Scaffold(
body: Center(
child: Text('Error: $error'),
),
);
},
);
}
}
Problem
The first time I build the app, I have no problem. But when I perform the hot reload, I get the following error message:
══════ Exception caught by widgets library ═══════════════════════════════════
The following Error was thrown building SwitchScreen(dirty, dependencies: [UncontrolledProviderScope], AsyncValue<AppUser?>.data(value: Instance of 'AppUser'), Instance of 'GoogleSignInService', Instance of 'AppUserController'):
Instance of 'Error'
The relevant error-causing widget was
SwitchScreen
lib\main.dart:67
When the exception was thrown, this was the stack
#0 StateNotifier.state=
package:state_notifier/state_notifier.dart:173
#1 AppUserController.setAppUser=
package:financesmanager/controllers/app_user_controller.dart:42
#2 SwitchScreen.build.<anonymous closure>
package:financesmanager/screens/switch_screen.dart:33
#3 _$AsyncData.when
package:riverpod/src/common.freezed.dart:148
#4 SwitchScreen.build
package:financesmanager/screens/switch_screen.dart:28
...
════════════════════════════════════════════════════════════════════════════════
E/flutter (13932): [ERROR:flutter/shell/common/shell.cc(103)] Dart Unhandled Exception: setState() or markNeedsBuild() called during build.
E/flutter (13932): This UncontrolledProviderScope 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.
E/flutter (13932): The widget on which setState() or markNeedsBuild() was called was:
E/flutter (13932): UncontrolledProviderScope
E/flutter (13932): The widget which was currently being built when the offending call was made was:
E/flutter (13932): SwitchScreen, stack trace: #0 Element.markNeedsBuild.<anonymous closure>
package:flutter/…/widgets/framework.dart:4217
E/flutter (13932): #1 Element.markNeedsBuild
package:flutter/…/widgets/framework.dart:4232
E/flutter (13932): #2 ProviderElement._debugMarkWillChange.<anonymous closure>
package:riverpod/…/framework/base_provider.dart:660
E/flutter (13932): #3 ProviderElement._debugMarkWillChange
package:riverpod/…/framework/base_provider.dart:664
E/flutter (13932): #4 ProviderStateBase.exposedValue=.<anonymous closure>
package:riverpod/…/framework/base_provider.dart:900
E/flutter (13932): #5 ProviderStateBase.exposedValue=
package:riverpod/…/framework/base_provider.dart:902
E/flutter (13932): #6 _StateNotifierProviderState._listener
package:riverpod/src/state_notifier_provider.dart:92
E/flutter (13932): #7 StateNotifier.state=
package:state_notifier/state_notifier.dart:162
E/flutter (13932): #8 AppUserController.setAppUser=
package:financesmanager/controllers/app_user_controller.dart:42
E/flutter (13932): #9 SwitchScreen.build.<anonymous closure>
package:financesmanager/screens/switch_screen.dart:33
Question
How can I solve the problem?
Thank you very much!
Update (2021-06-08)
In my main.dart file I have:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
runApp(ProviderScope(child: FMApp()));
}
class FMApp extends HookWidget {
FMApp({
Key? key,
}) : super(key: key);
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
#override
Widget build(BuildContext context) {
final darkTheme = AppTheme.theme(Brightness.dark);
final lightTheme = AppTheme.theme(Brightness.light);
final isLightTheme = useProvider<bool>(themePreferenceProvider);
final theme = isLightTheme ? lightTheme : darkTheme;
return FutureBuilder(
future: _initialization,
builder: (context, snapshot) {
if (snapshot.hasError) {
return FlutterFireInitErrorScreen(
appTitle: 'FM App',
darkTheme: darkTheme,
error: snapshot.error,
theme: theme,
);
}
if (snapshot.connectionState == ConnectionState.done) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'FM App',
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale.fromSubtags(languageCode: 'en'),
Locale.fromSubtags(languageCode: 'es'),
Locale.fromSubtags(languageCode: 'it'),
],
darkTheme: darkTheme,
theme: theme,
initialRoute: SwitchScreen.route,
routes: {
SwitchScreen.route: (context) => const SwitchScreen(),
},
);
}
return FlutterFireInitWaitingScreen(
appTitle: 'FM App',
darkTheme: darkTheme,
theme: theme,
);
},
);
}
}
Possible solution
For now I solved it by replacing, in switch_screen.dart file, this code:
final budgetsService = useProvider(budgetsServiceProvider);
final budgetsController = context.read<BudgetsController>(budgetsProvider.notifier);
budgetsController.retrieveBudgets(budgetsService);
with the following:
final budgetsService = BudgetsService(uid: data.uid);
context
.read(budgetsControllerProvider)
.retrieveBudgets(budgetsService);
What do you think? Is this a good solution? Is there a better one? Thank you!
The interpretation of the error is that two widgets are updating at the same time, probably because they watch the same provider.
When a Child Widget tries to rebuild while its Parent Widget also tries to rebuild, it generates this error. To solve this error, only the Parent Widget needs to rebuild, because the Child Widget will automatically rebuild.
Unfortunately, in the code you provide, I cannot see from where your SwitchScreen is displayed so I cannot tell you where the exact problem could be.

Displaying CircularProgressIndicator() will not stop after API call is completed

I'm attempting to have a CircularProgressIndicator display while the API call is made. When navigating to the OrdersScreen the CircularProgressIndicator displays and does not stop.
When clicking on the error it is directing me to my catch in my try{} catch{} block in my API call.
Here is the error I'm encountering:
I/flutter (22500): Invalid argument(s) (input): Must not be null
E/flutter (22500): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: Invalid argument(s) (input): Must not be null
[38;5;248mE/flutter (22500): #0 Orders.getOrders[39;49m
E/flutter (22500): <asynchronous suspension>
[38;5;248mE/flutter (22500): #1 _OrdersScreenState.initState.<anonymous closure> (package:shop_app/screens/order_screen.dart)[39;49m
E/flutter (22500): <asynchronous suspension>
E/flutter (22500):
Here is my API call:
class Orders with ChangeNotifier {
List<OrderItem> _orders = [];
List<OrderItem> get orders {
return [..._orders];
}
//make a copy of private class _orders
//establishing so that we cannot modify the private class
//READ API call
Future<void> getOrders() async {
final url = Uri.https(
'shop-app-flutter-49ad1-default-rtdb.firebaseio.com', '/products.json');
//note that for the post URL when using this https package we had to remove the special characters (https://) in order to properly post via the API
//establish the URL where the API call will be made
try {
final response = await http.get(url);
// print(json.decode(response.body));
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
//retrieve the json response data stored in firebase, translate to a Map, and store that map in the jsonResponse variable
if (jsonResponse == null) {
return;
}
//if there is no data returned in the jsonResponse (the db is empty) then we do nothing, avoiding an app crash on an empty API call
final List<OrderItem> orderProducts = [];
//establish an empty list in preparation to store the new Order values retrieved from the API call
jsonResponse.forEach((orderID, orderData) {
//forEach will exectue a function on every value that is housed within that Map
orderProducts.insert(
0, //insert at index 0 inserts the newest added product at the beginning of the list
OrderItem(
id: orderID,
amount: orderData['amount'],
dateTime: DateTime.parse(orderData['dateTime']),
products: (orderData['products'] as List<dynamic>)
.map(
(item) => CartItem(
id: item['id'],
title: item['title'],
quantity: item['quantity'],
price: item['price'],
),
)
.toList(),
//since products is stored on the db as a map, we have to retrieve those values and define how the properties of the items stored in the db should be mapped --> recreating our CartItem as it's stored in the db
));
//retrieve the values for each of the given properties and Map them according to the values stored on the server
});
_orders = orderProducts;
notifyListeners();
//set the value of the _items list - that is the primary data of the ProductsProvider to tell the different areas of the app the data to show - equal to the values retrieved from the API call
} catch (error) {
print(error);
throw (error);
}
}
}
Code with CircularProgressIndicator:
class OrdersScreen extends StatefulWidget {
static const routeName = '/orders';
#override
_OrdersScreenState createState() => _OrdersScreenState();
}
class _OrdersScreenState extends State<OrdersScreen> {
bool _isLoading = false;
#override
void initState() {
setState(() {
_isLoading = true;
});
// when the state of the screen is initialized set the value of _isLoading to true
// by setting _isLoading to true we are establishing another state while the API call is being made
Provider.of<Orders>(context, listen: false).getOrders().then((_) {
setState(() {
_isLoading = false;
});
});
// we are making the API call and then setting the state of _isLoading back to false indicating the change of the _isLoading variable means a completed API call
// --> by changing the value of _isLoading prior to and after the API call it allows us to put additional functionality while the API call is made --> we established a CircularProgressIndicator which may be found in the body
super.initState();
}
#override
Widget build(BuildContext context) {
final orderData = Provider.of<Orders>(context);
return Scaffold(
appBar: AppBar(
title: Text('Your Order'),
),
body: _isLoading == true
? Center(
child: CircularProgressIndicator(
backgroundColor: Theme.of(context).primaryColor),
)
: ListView.builder(
itemCount: orderData.orders.length,
itemBuilder: (ctx, index) => OrderCard(
order: orderData.orders[index],
),
//populate the order card UI element with data provided by the orders method within orders.dart
//this data is retrieved by calling the provider of type orders
),
drawer: SideDrawer(),
);
}
}
For reference:
OrderItem:
class OrderItem {
OrderItem({
#required this.id,
#required this.amount,
#required this.products,
#required this.dateTime,
});
final String id;
final double amount;
final List<CartItem> products; //CartItem from cart.dart
final DateTime dateTime;
}
CartItem:
class CartItem {
CartItem({
#required this.id,
#required this.title,
#required this.quantity,
#required this.price,
});
final String id;
final String title;
final int quantity;
final double price;
}
To fully take advantage of the Provider you already have setup, you should make the body of your scaffold a Consumer<Orders> widget. Keep the same logic inside, but it would need to be based on a bool (initialized to true) that lives within the Orders class.
Consumer<Orders>(builder: (context, orderData, child) {
return orderData.isLoading == true
? Center(
child: CircularProgressIndicator(
backgroundColor: Theme.of(context).primaryColor),
)
: ListView.builder(
itemCount: orderData.orders.length,
itemBuilder: (ctx, index) => OrderCard(
order: orderData.orders[index],
),
//populate the order card UI element with data provided by the orders method within orders.dart
//this data is retrieved by calling the provider of type orders
);
});
Handle the value of isLoading in your getOrders() function and that will notify the Consumer<Orders> widget to either return a CircularProgressIndicator or the ListView.builder once isLoading is updated to false.
You still call that function in initState but the local bool in that class would go away.

NoSuchMethodError: The method 'query' was called on null. (Although it brings the items to the UI)

Problem Summarization
I've been on stack and seen this question a lot, I tried every solution I could find but it didn't work for me. I'm retrieving some data from my sqflite db and sometimes it fetches the results and some times it doesn't which appears to be pretty odd. I've read that you shouldn't call init() function in the constructor of the Database.
It's said that this is this wrong
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
class TaskDbProvider {
Database db;
TaskDbProvider(){
init()
}
init() async {...}
}
How ever I've got a working example of the code above. (Although in my new approach it doesn't seem to work)
Their Solution
Listen to a Completer() stream
class TaskDbProvider {
Database db;
var readyCompleter = Completer();
Future get ready => readyCompleter.future;
TaskDbProvider(){
init().then((_) {
// mark the provider ready when init completes
readyCompleter.complete();
});
}
}
Although this triggers a new chain of exceptions, which i could provide if needed.
github link
The error
[ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: NoSuchMethodError: The method 'query' was called on null.
E/flutter (29065): Receiver: null
E/flutter (29065): Tried calling: query("Task")
E/flutter (29065): #0 Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
E/flutter (29065): #1 TaskDbProvider.fetchTaskList (package:oppo/resources/db_provider.dart:51:47)
Line 51
Future<List<Task>> fetchTaskList() async{
List<Map<String,dynamic>> list = await db.query('Task'); //line 51 (db is null?)
return List.generate(list.length, (i) {
return Task.fromDb(list[i]);
});
}
How I initialize the DB
Creating a BLoC (some extra functionality will be added later)
class BlocSpeech {
final cache = TaskDbProvider();
}
Make it available through the InheritedWidget (aka Provider)
class SpeechProvider extends InheritedWidget{
final BlocSpeech bloc ;
static BlocSpeech of(BuildContext context){
return (context.inheritFromWidgetOfExactType(SpeechProvider) as SpeechProvider).bloc;
}
SpeechProvider({Key key, Widget child})
: bloc = BlocSpeech(),
super(key: key,child: child);
#override
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
// TODO: implement updateShouldNotify
return true;
}
}
UI Widgets
Wrap the widget with the provider
SpeechProvider(child: Work())
and Build the Widget:
class Work extends StatefulWidget {
#override
_Work createState() => _Work();
}
class _Work extends State<Work> {
List<Task> allTasks=[];
#override
Widget build(BuildContext context) {
final SpeechBloc = SpeechProvider.of(context); //initialize the BLoC
final Future<List<Task>> future = SpeechBloc.cache.fetchTaskList(); //wait to fetch the items
future.then((value) => allTasks=value); //assign them to local variable
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: (allTasks.length),
itemBuilder: (BuildContext context, int index) {
return Container(
margin: EdgeInsets.only(top: 10, bottom: 10),
child: _taskWidget(index,SpeechBloc));
});
}
I will add any extra information if needed.
"Called on null" generally means you jumped the gun on a future call. Make sure that everything in the API that says "returns a future" or is "async" is in a method that returns a Future, and every call to a method that returns a Future is protected by an await. There are some places you can leave some of that out when you understand more carefully, but that's a good first stab at it.

can not retrieve model correctly through ScanStreamTransformer

I'm trying to retrieve an ItemModel class from my repository using streams implementing the BLoC logic in Flutter.
The problem is that the ItemModel is found but not retrieved . Therefore there is another loop triggered and tries to return a different Object, which is not retrieve on the Widgets.
Sample output:
I/flutter ( 4520): trying to get 25314126 from cache
I/flutter ( 4520): 25314126 in transformer
I/flutter ( 4520): 25314126 found !! returning: {id: 25314126, type: story, by: xucheng, time: 1607172267, text: , parent: null, kids: [25314805,25314781,25314684,25314664], dead: 0, deleted: 0, url: https://bitbashing.io/std-visit.html, score: 24, title: std::visit is everything wrong with modern C++ (2017), descendants: 4}
I/flutter ( 4520): ----------item to find: 25314126 and found: Instance of 'ItemModel'
I/flutter ( 4520): returned item Instance of 'ItemModel'
Here the correct Object is found and retrieved but not received on the UI and therefore there is another search right away:
I/flutter ( 4520): trying to get 25314805 from cache
I/flutter ( 4520): 25314805 found !! returning: {id: 25314805, type: comment, by: vasama, time: 1607178963, text: We got here because adding library features is a lot easier and less risky than adding language features. Work on pattern matching [1] is ongoing, but the bar for entry for a language feature is much higher and as such takes a longer time.<p>1. http://wg21.link/p1371, parent: 25314126, kids: [], dead: 0, deleted: 0, url: null, score: null, title: null, descendants: 0}
I/flutter ( 4520): ----------item to find: 25314805 and found: Instance of 'ItemModel'
I/flutter ( 4520): returned item Instance of 'ItemModel'
Which is not the Object I'm trying to retrieve!!!
There is always 'Loading 1' String shown
Here is my main Widget
class NewsDetail extends StatelessWidget{
final int itemId;
NewsDetail({ this.itemId}) ;
#override
Widget build(BuildContext context) {
final bloc = CommentsProvider.of(context);
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('Detail'),
),
body: buildBody(bloc),
);
}
Widget buildBody(CommentsBloc bloc){
return StreamBuilder(
stream: bloc.itemWithComments,
builder:
(context, AsyncSnapshot<Map<int,Future<ItemModel>>> snapshot){
if (!snapshot.hasData){
return Text('Loading 1');
}
final itemFuture = snapshot.data[itemId];
return FutureBuilder(
future: itemFuture,
builder: (context,AsyncSnapshot<ItemModel> itemSnapshot){
if(!itemSnapshot.hasData){
return Text('Loading 2');
}
return buildTitle(itemSnapshot.data);
}
);
},);
}
which indicates that is something wrong with the stream: bloc.itemWithComments,
Here is my BLOC class:
class CommentsBloc{
final _repository = Repository();
final _commentsFetcher = PublishSubject<int>();
final _commentsOutput = BehaviorSubject<Map<int,Future<ItemModel>>>();
//Streams
get itemWithComments => _commentsOutput.stream;
//Sink
Function(int) get fetchItemWithComments => _commentsFetcher.sink.add;
CommentsBloc(){
_commentsFetcher.stream.transform(_commentsTransformer()).pipe(_commentsOutput);
}
_commentsTransformer (){
return ScanStreamTransformer(
(cache,int id,index){
cache[id] = _repository.fetchItem(id);
print('$id in transformer');
cache[id].then((ItemModel item){
item.kids.forEach((kidId)=>fetchItemWithComments(kidId));
});
},
<int,Future<ItemModel>>{},
);
}
dispose(){
_commentsFetcher.close();
_commentsOutput.close();
}
}
And here is how i fetch items in my Repository class
List<Source> sources = <Source>[
newsDbProvider,
NewsApiProvider(),
];
List<Cache> caches = <Cache>[
newsDbProvider,
];
Future<ItemModel> fetchItem(int id) async{
ItemModel item;
var source;
for(source in sources) {
item = await source.fetchItem(id);
print('----------item to find: $id and found: ${item}');
if(item!=null){
break;
}
}
for(var cache in caches) {
if(cache!=source) {
cache.addItem(item);
}
}
print('returned item ${item}');
return item;
}
Even though there is an Instance returned every time why is the snapshot.hasData false?
Also please note that the widget gets invoked just once with the correct id by using onTap method. What have i completely missed?
Even though i used the fetchItemWithComments to sink the items, I still had to add a return statement in the ScanStreamTransformer
_commentsTransformer (){
return ScanStreamTransformer<int,Map<int,Future<ItemModel>>>(
(Map<int,Future<ItemModel>>cache,int id,index){
print(index);
cache[id] = _repository.fetchItem(id);
cache[id].then((ItemModel item){
item.kids.forEach( (kidId) => fetchItemWithComments(kidId) );
});
return cache; //was missing this
},
<int,Future<ItemModel>>{},
);
}
This had been bugging me for days, I went over everything and couldn't find anything so if you are using a ScanStreamTransformer class
Always have a return statement