Race condition in Stateful widget with parameter - flutter

I'm having a race condition of data from a BehaviorSubject's stream not populating/updating the state before the build function returns.
content-detail.dart
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:domain_flutter/application-bloc.dart';
import 'package:domain_flutter/content.dart';
import 'package:domain_flutter/tag_chips.dart';
import 'package:cached_network_image/cached_network_image.dart';
class ContentDetail extends StatefulWidget {
final String slug;
ContentDetail({Key key, this.slug}) : super(key: key);
#override
State<StatefulWidget> createState() => _ContentDetailState();
}
class _ContentDetailState extends State<ContentDetail> {
Content _content = Content();
_getContent() {
print(widget.slug);
applicationBloc.contentOutput.map( (contents) =>
contents.where((item) => item.slug == widget.slug).toList())
.listen((data) => {
if (this.mounted) {
setState(() => _content = data.first)
}
});
}
#override
void initState() {
super.initState();
_getContent();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: _content != null ? Text(_content.title) : Text(''),
),
body:
SafeArea(
child: Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: ListTile(
contentPadding: EdgeInsets.all(0),
title: Text(_content.title,style: TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(
DateFormat('dd.MM.yyyy').format(_content.changed),
style: TextStyle(fontSize: 12),
),
)),
TagChips(_content.tags),
CachedNetworkImage(
placeholder: (context, url) => CircularProgressIndicator(),
imageUrl: 'https://domain.tld/files/${_content.image}'),
],
),
),
),
);
}
}
The widget renders, but before it's rendered I receive an error.
If content isn't initialized I receive a different error.
What I mean by content initialized is
Content _content = Content();
content initialized:
The following assertion was thrown building ContentDetail(dirty, state: _ContentDetailState#15727):
A non-null String must be provided to a Text widget.
'package:flutter/src/widgets/text.dart':
Failed assertion: line 269 pos 10: 'data != null'
Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
https://github.com/flutter/flutter/issues/new?template=BUG.md
User-created ancestor of the error-causing widget was:
MaterialApp file:///home/darko/AndroidStudioProjects/domain_flutter/lib/main.dart:24:12
When the exception was thrown, this was the stack:
#2 new Text (package:flutter/src/widgets/text.dart:269:10)
#3 _ContentDetailState.build (package:domain_flutter/content_detail.dart:41:35)
#4 StatefulElement.build (package:flutter/src/widgets/framework.dart:4047:27)
#5 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3941:15)
#6 Element.rebuild (package:flutter/src/widgets/framework.dart:3738:5)
...
content uninitialized:
The following NoSuchMethodError was thrown building ContentDetail(dirty, state: _ContentDetailState#2be0b):
The getter 'title' was called on null.
Receiver: null
Tried calling: title
User-created ancestor of the error-causing widget was:
MaterialApp file:///home/darko/AndroidStudioProjects/domain_flutter/lib/main.dart:24:12
When the exception was thrown, this was the stack:
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
#1 _ContentDetailState.build (package:domain_flutter/content_detail.dart:53:38)
#2 StatefulElement.build (package:flutter/src/widgets/framework.dart:4047:27)
#3 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3941:15)
#4 Element.rebuild (package:flutter/src/widgets/framework.dart:3738:5)
...
So yeah, there is no data when it's trying to render the component... or Widget/s rather.
I should probably show my applicationBloc
import 'dart:async';
import 'package:domain_flutter/application-entities.dart';
import 'package:domain_flutter/content.dart';
import 'package:domain_flutter/tag.dart';
import 'package:rxdart/rxdart.dart';
class ApplicationBloc {
final _applicationEntities = ApplicationEntities();
Sink<List<Content>> get contentInput => _contentInputController.sink;
Sink<List<Tag>> get tagInput => _tagInputController.sink;
Stream<List<Content>> get contentOutput => _contentOutputSubject.stream;
Stream<List<Tag>> get tagOutput => _tagOutputSubject.stream;
final _contentInputController = StreamController<List<Content>>();
final _tagInputController = StreamController<List<Tag>>();
final _contentOutputSubject = BehaviorSubject<List<Content>>();
final _tagOutputSubject = BehaviorSubject<List<Tag>>();
ApplicationBloc() {
_contentInputController.stream.listen(_handleContentInput);
_tagInputController.stream.listen(_handleTagInput);
}
void dispose() {
_contentInputController.close();
_contentOutputSubject.close();
_tagInputController.close();
_tagOutputSubject.close();
}
void _handleContentInput(List<Content> contentList) {
_applicationEntities.updateContent(contentList);
_contentOutputSubject.add(contentList);
}
void _handleTagInput(List<Tag> tagList) {
_applicationEntities.updateTags(tagList);
_tagOutputSubject.add(tagList);
}
}
final applicationBloc = ApplicationBloc();
You probably guessed, the idea is to load the JSON from a web service then provide it application wide via global variable.
That works without errors for everything but the ContentDetail class.
This ContentDetail class is almost a copy of another component that does almost the same, it filters by a tag slug and renders a list of Content.
This here only wants 1 item from the stream, Content with a specific slug property.
class Content {
// ...
final String slug;
}
As you can see I'm passing the slug in the ContentDetail constructor and my _ContentDetailState is accessing its property via widget.slug.
The _getContent() function of the ContentListByTagSlug class in comparison:
void _getContent() {
applicationBloc.contentOutput.map(
(contents) => contents.where(
(item) => item.tags.any((tag) => tag.slug == widget.tag.slug)
).toList()
)
.listen((data) => {
if (this.mounted) {
setState(() => {content = data})
}
});
}
This works, while only getting 1 item from it doesn't (see 1st code snippet).
This is how I define the FlatButton to open the ContentDetail page:
FlatButton(
child: Text('Read more'),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ContentDetail(slug: content[index].slug))),
),
It's valid, the slug is passed as the result of the print function indicates.
I'm not sure what to do to ascertain that the _content variable is populated before the build function is executed.
In Angular one creates a resolver, which then populates data before the component is created/initialized.
In Flutter?
Note I'm not using the bloc package, but put the streams in a dedicated class, then use setState to update the state in the listening classes, as the bloc package seems overkill for this scenario.
3 widgets (4 if you count the drawer)
1 displaying an unfiltered list
2 displaying a list filtered by tag slug
3 displaying one item filtered by tag slug
only the latter has errors.
update1: I even removed the if (this.mounted) check in _getContent() and I'm calling _getContent() again in the builder if _content is null.
Then I have changed _content to be a List and am getting _content.first.title which results in
Bad state: No element
But the widget is still rendered correctly.
So it seems that there are 2 invocations of the widget. One that throws errors, which is discarded and one that doesn't which is kept. I'm not familiar with the internals so that's my best guess.

This is answered in this answer.
What I'm taking away from it is
don't initialize variables that are meant to be filled by a stream
provide a check in the builder whether the value to be filled in null
if null render a loading screen else render the desired widget with populated state

Related

looking up a deactivated widget's ancestor is unsafe => using Riverpod => using "Navigator.of(context).pushReplacementNamed('/page');"

The scenario which I did and caused this Error: 1. after Login page created when I used hot-reload button 2. when I pressed Login-button and state of the page changed.
Recently I decided to use riverpod package in my flutter application, so I used hooks_riverpod: ^1.0.0-dev.7 with flutter_hooks: ^0.18.0 But when I create my LoginScreen with the help of Riverpod and Hooks I faced with problems which I provided my log in below.
LoginScreen:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lambda/configs/sizes/index.dart';
import 'package:lambda/configs/strings.dart';
import 'package:lambda/core/validator/src/mobile_number_validator.dart';
import 'package:lambda/presentation/state_notifiers/auth/index.dart';
import 'package:lambda/presentation/utils/input_formatter/index.dart';
import 'package:lambda/presentation/widgets/alert_message/alert_messge.dart';
import 'package:lambda/presentation/widgets/background/background.dart';
import 'package:lambda/presentation/widgets/progress/progress.dart';
import 'package:lambda/presentation/widgets/spacer/spacer.dart';
import 'package:lambda/routes.dart';
class LoginScreen extends HookConsumerWidget with MobileNumberValidator {
LoginScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context,WidgetRef ref) {
ref.listen<AuthState>(authStateNotifierProvider, (state) {
state.maybeWhen(
orElse: () {},
otpSent: (mobileNumber) {
AppNavigator.replaceWith<String>(
NavigationPaths.verifyLogin, mobileNumber);
},
error: (message) {
AlertMessage(context).warning(message);
});
});
final phoneFieldController = useTextEditingController();
return NormalBackground(
child: Scaffold(
body: Padding(
padding: EdgeInsets.symmetric(
horizontal: LayoutSizes(context).responsive(60)),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
Strings.pleaseEnterYourMobileNumberForLoginToTheLambda,
style: Theme.of(context).textTheme.caption,
textAlign: TextAlign.center,
),
VSpacer(LayoutSizes(context).marginXXL),
TextFormField(
controller: phoneFieldController,
style: Theme.of(context).textTheme.caption,
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
inputFormatters: [PersianNumberFormatter()],
decoration: const InputDecoration(
hintText: Strings.mobileNumberHint,
),
),
VSpacer(LayoutSizes(context).marginL),
ref.watch(authStateNotifierProvider).maybeMap(
orElse: () {
return ElevatedButton(
onPressed: () {
if (isValidIRMobileNumber(phoneFieldController.text)) {
ref
.read(authStateNotifierProvider.notifier)
.sendOtp(phoneFieldController.text);
} else {
AlertMessage(context).warning(
Strings.isInvalidInput(Strings.mobileNumber));
}
},
style: ButtonStyle(
fixedSize: MaterialStateProperty.all(
Size(double.maxFinite,
LayoutSizes(context).buttonHeightL),
),
),
child: const Text(Strings.next),
);
},
loading: (_) {
return const CircularProgress();
},
),
],
),
),
),
);
}
}
AuthStateProviderNotifier:
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lambda/configs/strings.dart';
import 'package:lambda/core/extensions/strings.dart';
import 'package:lambda/data/repositories/auth/authentication_repository.dart';
import 'package:lambda/services/http/index.dart';
import 'package:lambda/services/logger/logger.dart';
import 'auth_state.dart';
final authStateNotifierProvider =
StateNotifierProvider<AuthStateNotifier, AuthState>((ref) {
final authRepository = ref.read(authRepositoryProvider);
return AuthStateNotifier(authRepository);
});
class AuthStateNotifier extends StateNotifier<AuthState> {
final AuthenticationRepository _repository;
AuthStateNotifier(this._repository) : super(const AuthState.initial());
Future<void> sendOtp(String mobileNumber) async {
try {
state = const AuthState.loading();
await _repository.sendValidationCode(
mobileNumber: mobileNumber.convertToEnNum());
state = AuthState.otpSent(mobileNumber: mobileNumber);
} catch (e, s) {
_handleError(e, s);
}
}
Future<void> verifyOtp(String mobileNumber, String code) async {
try {
state = const AuthState.loading();
await _repository.login(
mobileNumber: mobileNumber.convertToEnNum(),
verificationCode: code.convertToEnNum());
state = const AuthState.authenticated();
} catch (e, s) {
_handleError(e, s);
}
}
void _handleError(Object e, StackTrace s) {
Logger().info('error : $e stack: $s');
if (e is NetworkExceptionX) {
state = AuthState.error(
errorMessage: e.messageForUser ?? Strings.someErrorHappened);
} else {
state = const AuthState.error(errorMessage: Strings.someErrorHappened);
}
}
}
Run:
======== Exception caught by widgets library =======================================================
The following assertion was thrown building LoginScreen(dirty, dependencies: [_LocalizationsScope-[GlobalKey#aacaf], UncontrolledProviderScope, _InheritedTheme], state: _ConsumerState#cf20e, useTextEditingController: TextEditingController#f5c6d(TextEditingValue(text: ┤├, selection: TextSelection(baseOffset: -1, extentOffset: -1, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1)))):
Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer stable.
To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
The relevant error-causing widget was:
LoginScreen file:///Users/taleb/FlutterProjects/lambda/lib/routes.dart:40:36
When the exception was thrown, this was the stack:
#0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:3944:9)
#1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:3958:6)
#2 Element.findAncestorWidgetOfExactType (package:flutter/src/widgets/framework.dart:3996:12)
#3 debugCheckHasMediaQuery.<anonymous closure> (package:flutter/src/widgets/debug.dart:218:50)
#4 debugCheckHasMediaQuery (package:flutter/src/widgets/debug.dart:234:4)
...
====================================================================================================
======== Exception caught by widgets library =======================================================
The following assertion was thrown building LoginScreen(dirty, dependencies: [_LocalizationsScope-[GlobalKey#aacaf], UncontrolledProviderScope, _InheritedTheme], state: _ConsumerState#cf20e, useTextEditingController: TextEditingController#f5c6d(TextEditingValue(text: ┤├, selection: TextSelection(baseOffset: -1, extentOffset: -1, affinity: TextAffinity.downstream, isDirectional: false), composing: TextRange(start: -1, end: -1)))):
Looking up a deactivated widget's ancestor is unsafe.
At this point the state of the widget's element tree is no longer stable.
To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method.
The relevant error-causing widget was:
LoginScreen file:///Users/taleb/FlutterProjects/lambda/lib/routes.dart:40:36
When the exception was thrown, this was the stack:
#0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:3944:9)
#1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:3958:6)
#2 Element.findAncestorWidgetOfExactType (package:flutter/src/widgets/framework.dart:3996:12)
#3 debugCheckHasMediaQuery.<anonymous closure> (package:flutter/src/widgets/debug.dart:218:50)
#4 debugCheckHasMediaQuery (package:flutter/src/widgets/debug.dart:234:4)
...
====================================================================================================
These errors happened when I used TextField in HookConsumerWidget
class.
I am using HookConsumerWidget instead of StatefullWidget.
I also tried to use StatefullConsumerWidget but the problem was not > solved.(ConsumerStatefulWidget+riverPod).
My question is how can we use Textfield in HookConsumerWidget + Riverpod ????
If you want to run it by yourself, I Provided a sample code of this error on my Github:
smaple_hook_riverpod
I think "Navigator.of (context).pushReplacementNamed" is not the right solution for "Flutter Navigator + Riverpod".
A better solution is an entire navigation stack (which is a List of Page's) to create from a list of immutable objects. The problem of navigation is than reduced to manipulation an immutable collection.
final navigationStackProvider = Provider<>((ref) => [Obj1, Obj2, Obj3]);
[Obj1, Obj2, Obj3] je následně synchronizován s navigation stack, odpovídající [Screen1, Screen2, Screen3]...
I prepared example that implements a simple login logic, where some pages are not available without login.
Example is here: riverpod_navigator_example
The cause of this issue is that you're calling Navigator on Widget build. You may want to consider moving ref.listen<AuthState> on initState. Then wrap the Navigator with a SchedulerBinding to wait for the rendering state to finish before navigating.
SchedulerBinding.instance.addPostFrameCallback((_) {
AppNavigator.replaceWith<String>(NavigationPaths.verifyLogin, mobileNumber);
});

Bad state: Tried to read a provider that threw during the creation of its value. The exception occurred during the creation of type SomeBloc

I get this error while using bloc not provider.
while trying to add event to SomeBloc that is created before It gives this error :
======== Exception caught by gesture ===============================================================
The following StateError was thrown while handling a gesture:
Bad state: Tried to read a provider that threw during the creation of its value.
The exception occurred during the creation of type EditFooterCubit.
When the exception was thrown, this was the stack:
#0 _CreateInheritedProviderState.value (package:provider/src/inherited_provider.dart:661:7)
#1 _CreateInheritedProviderState.debugFillProperties (package:provider/src/inherited_provider.dart:750:44)
#2 _InheritedProviderScopeElement.debugFillProperties (package:provider/src/inherited_provider.dart:585:20)
#3 DiagnosticableNode.builder.<anonymous closure> (package:flutter/src/foundation/diagnostics.dart:2945:17)
#4 DiagnosticableNode.builder (package:flutter/src/foundation/diagnostics.dart:2948:8)
...
Handler: "onTap"
Recognizer: TapGestureRecognizer#445b5
debugOwner: GestureDetector
state: ready
won arena
finalPosition: Offset(188.8, 422.3)
finalLocalPosition: Offset(36.1, 20.3)
button: 1
sent tap down
====================================================================================================
This is where I create the bloc :
class EditFooterPage extends StatelessWidget {
static int pageNumber() => 5;
#override
Widget build(BuildContext context) {
return BlocProvider<EditFooterCubit>(
create: (context) => EditFooterCubit(
footerRepo: RepositoryProvider.of<FooterRepository>(context),
footerBloc: BlocProvider.of<FooterBloc>(context),
),
child: EditFooterForm(),
);
}
}
And here bloc is used to add event :
class EditFooterForm extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
ElevatedButton(
onPressed: () {
//here on button press error happens.
context.read<EditFooterCubit>().footerPreviewRequestedToState();
},
child: Text('UpdateFooterPart')),
SizedBox(height: 10),
FooterPart(),
],
),
),
);
}
}
I can't find why It gives error since I created this page like LoginPage and Login Form Example in https://bloclibrary.dev/
As the error indicates, error occurred during creation of SomeBloc .
So input parameters of SomeBloc are not passed correctly.
For example
SomeBloc({
#required this.blocA,
})
SomeBloc needs BLocA and when you want to create SomeBloc you have passed BlocA like this :
BlocProvider<SomeBloc >(
create: (context) => SomeBloc (//here SomeBloc is the error causing bloc because
blocA: BlocProvider.of<BlocA>(context),//here BlocA is not accessible in this page or subtree so will give error
),
child: _View(),
);
But BLocA is not accessible in this subTree.
So you should Check the input parameters of the bloc that causes error
and see whether you have passed the parameters correctly or not because the reason of error is that the passed parameters are not accessible in this subtree.

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.

I/flutter ( 4037): The method '[]' was called on null

I am getting an error trying to run this code about the method '[]' being null. Is it maybe the list view? The output doesn't even show up in debug console anymore. Sorry I'm new to this.
The code:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class GetStats extends StatefulWidget {
#override
_GetStatsState createState() => _GetStatsState();
}
class _GetStatsState extends State<GetStats> {
Map data;
Future<String> getData() async {
var response = await http.get(
Uri.encodeFull(
'http://api.steampowered.com/ISteamUserStats/GetUserStatsForGame/v0002/?appid=730&key=D5F4E0DED484F47380C2804A529BAEDC&steamid=76561198406742636'),
headers: {"Accept": "application/json"});
setState(() {
data = json.decode(response.body);
});
print(data["playerstats"]["stats"][0]["value"]);
}
#override
void initState() {
getData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('CSGO STATS'),
centerTitle: true,
),
body: ListView.builder(
itemCount: 20,
itemBuilder: (
BuildContext context,
int index,
) {
return Card(
child: Text(
data["playerstats"],
),
);
}),
);
}
}
Error
I/flutter ( 4037): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 4037): The following NoSuchMethodError was thrown building:
I/flutter ( 4037): The method '[]' was called on null.
I/flutter ( 4037): Receiver: null
I/flutter ( 4037): Tried calling: []("playerstats")
I/flutter ( 4037):
I/flutter ( 4037): When the exception was thrown, this was the stack:
What am I doing wrong? Do I need to initialize something?
You are making correct http request and getting data too
However the problem lies in your widget , I checked the API response it is throwing a response of Map<String,dynamic>
While displaying the data you are accessing the data using playerstats key which in turn is giving you a Map which is not a String as required by Text Widget in order to display it !
You can display the data by simply converting it to String by using toString() method like this
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('CSGO STATS'),
centerTitle: true,
),
body: ListView.builder(
itemCount: 20,
itemBuilder: (
BuildContext context,
int index,
) {
return Card(
child: Text(
data["playerstats"].toString(),
),
);
}),
);
}
Also, I would like to suggest you some Improvements in your Code
Use type annotation as much as possible, It makes your code more safe and robust , Like you have declared data variable as Map data; , You should declare it like Map<String,dynamic> data;
I think you forget to call super.initState() in your initState() method , Make sure to call it before all your methods.
Method getData doesn't return anything, So make it as Future<void> instead of Future<String>
Since you are new contributor to StackOverflow , I welcome you !
Happy Fluttering !
data["playerstats"] is a Map while the Text widget needs String.
Your method is ok, but the problem is in initiating the text widget.
child: Text(
data["playerstats"],
),
['playerstats'] is not a single text, its a map of list. You need to specify the exact text field name you want to see. Still it will show you full data if you add .toString() with the field name.

Flutter: Can't call Provider<T>.of(context) from a function that is defined into another file. ProviderNotFoundException

I'm new to flutter and I wish to organize my folders in order to write cleaner code.
I'm trying to divide my flutter page on three parts:
login_view.dart (this view will be rendered as the login view and will call functions defined into login_builder.dart to build each of its widgets)
login_builder.dart (contains function definitions called by login_view.dart to build widgets)
login_state.dart (for state management)
But when I call Provider.of(context) inside a functiton that is defined into login_builder.dart (out of the login_view.dart widget tree) it always throws ProviderNotFoundException
// login_view.dart
import 'package:discover/ui/views/login/login_builder.dart';
import 'package:discover/ui/views/login/login_state.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Login extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<LoginState>(
create: (context) => LoginState(),
child: buildLoginForm(context),
);
}
}
// login_builder.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:discover/ui/views/login/login_state.dart';
Widget buildLoginForm(BuildContext context) {
var loginState = Provider.of<LoginState>(context);
return Form(
child: Column(
children: <Widget>[
TextFormField(
onChanged: (value) => loginState.userName = value,
)
],
),
);
}
// login_state.dart
import 'package:flutter/material.dart';
class LoginState extends ChangeNotifier {
String _userName = "";
String get userName => _userName;
set userName(String userName) {
_userName = userName;
}
}
// Debug Console
════════ Exception caught by widgets library ═══════════════════════════════════
The following ProviderNotFoundException was thrown building Login(dirty):
Error: Could not find the correct Provider<LoginState> above this Login Widget
To fix, please:
* Ensure the Provider<LoginState> is an ancestor to this Login Widget
* Provide types to Provider<LoginState>
* Provide types to Consumer<LoginState>
* Provide types to Provider.of<LoginState>()
* Ensure the correct `context` is being used.
If none of these solutions work, please file a bug at:
https://github.com/rrousselGit/provider/issues
The relevant error-causing widget was
Login
When the exception was thrown, this was the stack
Provider.of
buildLoginForm
Login.build
StatelessElement.build
ComponentElement.performRebuild
The context you passed to login_builder is the same context that was passed to login_view, so it exists in a place of the widget tree above where you inserted your ChangeNotifierProvider which is why you can't find it with Provider.of. In order to get this to work the way you want it to, you need to utilize a Builder widget to gain a new BuildContext that exists below the provider:
class Login extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<LoginState>(
create: (context) => LoginState(),
child: Builder(builder: buildLoginForm),
);
}
}
Now the context passed to buildLoginForm will be the context for the Builder (which exists below the provider) instead of for your Login widget (which exists above the provider).