Flutter Argument passing - Getting a null from routeArgs - flutter

I have the following setup of my flutter app:
HomeScreen.Dart
Widget build(BuildContext context) {
return Scaffold(
appBar: homeAppBar(context),
bottomNavigationBar: BottomNavBar(),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
CategoryList(),
ItemList(),
],
),
));
}
}
The CategoryList class contains a list view of category items. When a category item is clicked, the associated products should be displayed for that item. The way I do that is by passing the category name to the product item list class. Please see below:
CategoryItem.dart
class CategoryItemN extends StatelessWidget {
final String title;
const CategoryItemNew({
#required this.title,
});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => selectCategory(context),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
child: Column(
children: <Widget>[
Text(
title,
style: isActive
? TextStyle(
color: kTextColor,
fontWeight: FontWeight.bold,
)
: TextStyle(fontSize: 12),
),
if (isActive)
Container(
margin: EdgeInsets.symmetric(vertical: 5),
height: 3,
width: 22,
decoration: BoxDecoration(
color: kPrimaryColor,
borderRadius: BorderRadius.circular(10),
),
),
],
),
),
);
}
void selectCategory(BuildContext ctx) {
Navigator.of(ctx).pushNamed(
ItemList.routeName,
arguments: {
'title': title ==== > This is the category name
},
);
}
}
ItemList.dart
class _ItemListState extends State<ItemList> {
var _isInit = true;
var _isLoading = false;
var title = '';
void didChangeDependencies() {
if (_isInit) {
setState(() {
_isLoading = true;
});
final routeArgs =
ModalRoute.of(context).settings.arguments as Map<String, String>;
title = routeArgs['title']; ===================> This is where the error occurs!
Provider.of<Products>(context).fetchProducts(title, true).then((_) {
setState(() {
_isLoading = false;
});
});
}
_isInit = false;
super.didChangeDependencies();
}
Here's the error message:
The following NoSuchMethodError was thrown building Listener: The method '[]' was called on null. Receiver: null Tried calling []("title")

You can get arguments through 'ModalRoute.of(context).settings.arguments' in the build().
final routeArgs =
ModalRoute.of(context).settings.arguments as Map<String, String>;
There is a official example about 'Pass arguments to a named route'.
https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments

Related

Why does text form field re-render when user clicks on it in Flutter?

Why does text form field re-render when user clicks on it in Flutter?
My Flutter form contains a TextFormField for Name. When my user clicks on it, the entire form immediately re-renders (reloads), making it impossible for my user to enter anything.
Code
The TextFormField is commented so that you can easily find it.
You'll notice that this page also contains a second field that works perfectly. It's a Switch inside a StatefulBuilder that handles setting a TextEditingController for _importantController.
<!-- language: flutter -->
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart';
class CrudPage2 extends StatefulWidget {
final String docId;
const CrudPage2({Key? key, required this.docId}) : super(key: key);
#override
CrudPage2State createState() => CrudPage2State();
}
class CrudPage2State extends State<CrudPage2> {
late String name = "";
late bool isImportant = false;
final TextEditingController _nameController = TextEditingController();
final TextEditingController _importantController = TextEditingController();
Stream<DocumentSnapshot<Object?>> groceryItem(docID) =>
FirebaseFirestore.instance
.collection("groceries")
.doc(docID)
.snapshots();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
),
title: Text("Grocery Item"),
),
body: SizedBox(
width: double.infinity,
child: Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom + 20),
child: StreamBuilder<DocumentSnapshot>(
stream: groceryItem(widget.docId),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> streamSnapshot) {
if (streamSnapshot.connectionState == ConnectionState.waiting) {
print("****** Loading ******"); // debugging
return const Text("Loading");
} else if (streamSnapshot.hasData) {
if (widget.docId != "NEW") {
// Retrieve existing item
var jsonData = streamSnapshot.data?.data();
Map<String, dynamic> myData = jsonData as Map<String, dynamic>;
name = myData['name'];
isImportant = myData['important'];
}
_nameController.text = name;
if (isImportant) {
_importantController.text = "true";
} else {
_importantController.text = "false";
}
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//--------------------------------------------------------
// PROBLEM: Clicking on this field re-renders entire form.
Flexible(
child: TextFormField(
controller: _nameController,
decoration: const InputDecoration(labelText: 'Name'),
),
),
//--------------------------------------------------------
// No problem with this switch
StatefulBuilder(
builder: (BuildContext context, StateSetter importantStateSetter) {
return Row(
children: [
const Text("Important: "),
Switch(
value: isImportant,
onChanged: (value) {
importantStateSetter(() => isImportant = value);
},
),
],
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
child: ElevatedButton(
child: const Text('Cancel'),
onPressed: () async {
Navigator.of(context).pop();
}),
),
const SizedBox(
width: 10,
),
SizedBox(
child: ElevatedButton(
child: const Text("Submit"),
onPressed: () async {
final String name = _nameController.text;
if (widget.docId == 'NEW') {
addGroceryItem(name, 1.0, "test",
isImportant);
} else {
updateGroceryItem(widget.docId, name,
1.0, "test", isImportant);
}
// Clear the text fields
_nameController.text = '';
_importantController.text = "";
// Hide the bottom sheet
Navigator.of(context).pop();
},
),
)
],
),
],
);
} else {
return const Text("No Data");
}
})
),
),
);
} // Widget Build
//-------------------------------------------------------------
// Add New Grocery Item
//-------------------------------------------------------------
Future<void> addGroceryItem(
String name, double quantity, String category, bool isImportant) async {
await FirebaseFirestore.instance.collection('groceries').add({
"active": true,
"name": name,
"quantity": quantity,
"category": category,
"important": isImportant
});
}
//-------------------------------------------------------------
// Update Existing Grocery Item
//-------------------------------------------------------------
Future<void> updateGroceryItem(String docID, String name, double quantity,
String category, bool isImportant) async {
await FirebaseFirestore.instance.collection('groceries').doc(docID).update({
"active": true,
"name": name,
"quantity": quantity,
"category": category,
"important": isImportant
});
}
}
I added print("****** Loading ******"); line to help debug. When user clicks on text form field, the Console displays:
I/flutter (28767): ****** Loading ******
I/flutter (28767): ****** Loading ******
Why is the stream refreshing every time this widget is clicked?
Thank you for your time!
After a lot of Googling, I decided that my problem was coming from doing this entirely wrong. Here are some of the changes I made:
Pass-in values as JSON object parameter
Eliminate call to Firebase
Eliminate Stream Builder
Code below solves my problem using these changes:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class CrudPage2 extends StatefulWidget {
final String docId;
final Object? docSnap;
const CrudPage2({Key? key,
required this.docId,
required this.docSnap})
: super(key: key);
#override
CrudPage2State createState() => CrudPage2State();
}
class CrudPage2State extends State<CrudPage2> {
//--- Form State Variables...
late String name = "";
late bool isImportant = false;
//--- Controllers for Form Fields...
final TextEditingController _nameController = TextEditingController();
final TextEditingController _importantController = TextEditingController();
#override
initState() {
super.initState();
if (widget.docId != "NEW") {
Map<String, dynamic> myData = widget.docSnap as Map<String, dynamic>;
name = myData['name'];
isImportant = myData['important'];
}
_nameController.text = name;
if (isImportant) {
_importantController.text = "true";
} else {
_importantController.text = "false";
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
),
title: Text("Grocery Item"),
),
body: SizedBox(
width: double.infinity,
child: Padding(
padding: EdgeInsets.only(
top: 20,
left: 20,
right: 20,
bottom: MediaQuery.of(context).viewInsets.bottom + 20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: _nameController,
decoration: const InputDecoration(labelText: 'Name'),
),
StatefulBuilder(
builder:
(BuildContext context, StateSetter importantStateSetter) {
return Row(
children: [
const Text("Important: "),
Switch(
value: isImportant,
onChanged: (value) {
importantStateSetter(() => isImportant = value);
},
),
],
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 110,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(
Colors.grey),
padding: MaterialStateProperty.all(
const EdgeInsets.all(5)),
textStyle: MaterialStateProperty.all(
const TextStyle(fontSize: 24))),
child: const Text('Cancel'),
onPressed: () async {
Navigator.of(context).pop();
}),
),
SizedBox(
width: 200,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.green),
padding: MaterialStateProperty.all(
const EdgeInsets.all(5)),
textStyle: MaterialStateProperty.all(
const TextStyle(fontSize: 24))),
child: const Text("Submit"),
onPressed: () async {
final String name = _nameController.text;
if (widget.docId == 'NEW') {
addGroceryItem(name, 1.0, "Test",
isImportant);
} else {
updateGroceryItem(widget.docId, name,
1.0, "Test", isImportant);
}
// Clear the text fields
_nameController.text = '';
_importantController.text = "";
// Hide the bottom sheet
Navigator.of(context).pop();
},
),
)
],
),
]
)
)
),
);
} // Widget Build
Future<void> addGroceryItem(
String name, double quantity, String category, bool isImportant) async {
await FirebaseFirestore.instance.collection('groceries').add({
"active": true,
"name": name,
"quantity": quantity,
"category": category,
"important": isImportant
});
}
Future<void> updateGroceryItem(String docID, String name, double quantity,
String category, bool isImportant) async {
await FirebaseFirestore.instance.collection('groceries').doc(docID).update({
"active": true,
"name": name,
"quantity": quantity,
"category": category,
"important": isImportant
});
}
}
Any comments and/or suggestions are still appreciated.
I hope this helps someone else in the future.

Exception caught by widgets library - Bad state

import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class ChatScreen extends StatefulWidget {
static const String screenRoute = 'chat_screen';
const ChatScreen({Key? key}) : super(key: key);
#override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _firestore = FirebaseFirestore.instance;
final _auth = FirebaseAuth.instance;
late User signedInUser;
String? messageText;
#override
void initState() {
super.initState();
getCurrentUser();
}
void getCurrentUser() {
try {
final user = _auth.currentUser;
if (user != null) {
signedInUser = user;
print(signedInUser.email);
}
} catch (e) {
print(e);
}
}
//void getMessages() async {
// final messages = await _firestore.collection('messages').get();
//for (var message in messages.docs) {
// print(message.data());
// }
//}
void messagesStream() async {
await for (var snapshot in _firestore.collection('messages').snapshots()) {
for (var message in snapshot.docs) {
print(message.data());
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.yellow[900],
title: Row(
children: [
Image.asset('assets/images/logo.png', height: 25),
const SizedBox(width: 10),
const Text('ChatMe'),
],
),
actions: [
IconButton(
onPressed: () {
messagesStream();
//_auth.signOut();
//Navigator.pop(context);
},
icon: const Icon(Icons.download),
),
],
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('messages').snapshots(),
builder: (context, snapshot) {
List<Text> messageWidgets = [];
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(
backgroundColor: Colors.blue,
),
);
}
final messages = snapshot.data!.docs;
for (var message in messages) {
final messageText = message.get('text');
final messageSender = message.get('sender');
final messageWidget = Text('$messageText - $messageSender');
messageWidgets.add(messageWidget);
}
return ListView(
children: messageWidgets,
);
},
),
Container(
decoration: const BoxDecoration(
border: Border(
top: BorderSide(
color: Colors.orange,
width: 2,
),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: TextField(
onChanged: (value) {
messageText = value;
},
decoration: const InputDecoration(
contentPadding: EdgeInsets.symmetric(
vertical: 10,
horizontal: 20,
),
hintText: 'Write Your Message Here..',
border: InputBorder.none,
),
),
),
TextButton(
onPressed: () {
_firestore.collection('messages').add({
'text': messageText,
'sender': signedInUser.email,
});
},
child: Text(
'send',
style: TextStyle(
color: Colors.blue[800],
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
],
),
),
],
),
),
);
}
}
════════ Exception caught by widgets library ═══════════════════════════════════
Bad state: field does not exist within the DocumentSnapshotPlatform
The relevant error-causing widget was
StreamBuilder<QuerySnapshot<Object?>>
lib\…\screens\chat_screen.dart:82
════════════════════════════════════════════════════════════════════════════════

The method 'getProducts' was called on null

i am trying to retrieve data using rest api from woocommerce website using flutter
this is the api for retrieve json data
Future<List<Product>> getProducts(String tagId) async {
List<Product> data = new List<Product>();
try {
String url = Config.url +
Config.productsURL +
"?consumer_key=${Config.key}&consumer_secret=${Config.secret}&tag=$tagId";
var response = await Dio().get(url,
options: new Options(
headers: {HttpHeaders.contentTypeHeader: "application/json"}));
if (response.statusCode == 200) {
data = (response.data as List).map((i) => Product.fromJson(i),).toList();
}
} on DioError catch (e) {
print(e.response);
}
return data;
}
this is the widget to handle the data to the mobile app
class WidgetHomeProducts extends StatefulWidget {
WidgetHomeProducts({Key key, this.labelName, this.tagId}) : super(key : key);
String labelName;
String tagId;
#override
_WidgetHomeProductsState createState() => _WidgetHomeProductsState();
}
class _WidgetHomeProductsState extends State<WidgetHomeProducts> {
APIServices apiServices;
#override
void initState() {
apiServices = new APIServices();
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
color: const Color(0xffF4F7FA),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 16, top: 4),
child: Text(
this.widget.labelName,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
Padding(
padding: EdgeInsets.only(left: 16, top: 4),
child: FlatButton(
onPressed: () {},
child: Text(
'View All',
style: TextStyle(color: Colors.blueAccent),
),
),
),
],
),
_productList(),
],
),
);
}
Widget _productList(){
return new FutureBuilder(
future: apiServices.getProducts(this.widget.tagId),
builder: (BuildContext context, AsyncSnapshot<List<Product>> model){
if(model.hasData){
return _buildList(model.data);
}if(model.hasError){
print("error");
}
return Center(child: CircularProgressIndicator(),);
});
}
i got The method error message that says
'getProducts' was called on null.
Receiver: null
Tried calling: getProducts("971")
can anyone help me to fix this?

Problem when Displaying the fetched data for the first time in flutter App

In My Application, I fetch data from an API and save the data onto a List. When I try to display the data on the screen, it gives a red screen with a warning for the first few seconds and then loads the data into the screen.
compiler gives this warning message to console
════════ Exception caught by widgets library ═══════════════════════════════════
The following StateError was thrown building StatusScreen(dirty, dependencies: [_InheritedProviderScope<CovidData>], state: _StatusScreenState#54e51):
Bad state: No element
The relevant error-causing widget was
StatusScreen
When the exception was thrown, this was the stack
#0 List.first (dart:core-patch/growable_array.dart:332:5)
#1 _StatusScreenState.build
#2 StatefulElement.build
#3 ComponentElement.performRebuild
#4 StatefulElement.performRebuild
...
════════════════════════════════════════════════════════════════════════════════
This is the status screen
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:bubble_tab_indicator/bubble_tab_indicator.dart';
import 'package:Health_app/data/covidData.dart'; /* ---> the fetched data method and List of data is from here */
import 'package:Health_app/screens/covidBarChart.dart'; /*-----> stateless widget that renders a barchart from provided data */
import 'package:Health_app/config/palatte.dart';
import 'package:Health_app/config/styles.dart';
import 'package:Health_app/widgets/custom_app_bar.dart';
import 'package:Health_app/widgets/statusGridViewer.dart'; /*----->stateless widget that provide information based on the bool values provided */
class StatusScreen extends StatefulWidget {
static const routeName = '/statusScreen';
#override
_StatusScreenState createState() => _StatusScreenState();
}
class _StatusScreenState extends State<StatusScreen> {
var _isInit = true;
var _isLoading = false;
var _local = true;
var _today = true;
get local {
return _local;
}
get today {
return _today;
}
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
void didChangeDependencies() {
print("Didchangedependanciescalled");
if (_isInit) {
setState(() {
_isLoading = true;
});
Provider.of<CovidData>(context, listen: true)
.fetchAndSetDataCovid()
.then((_) => {
setState(() {
_isLoading = false;
}),
});
}
_isInit = false;
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
/* ----> I think the problem occurs from here,
when I'm calling the Provider to obtain the fetched data, the data obtained is stored in a `List<CovidProperties>` named **data**.
final covidData = Provider.of<CovidData>(context, listen: false);
final dataInstance = covidData.data;
final List<String> numList = [];
final List<String> numList2 = [];
final List<int> newNumList;
/*Here I'm selecting the 'pcrData' list, it consist of key value pairs of data,
where value is another list of values with 'pcr_count' and 'date' */
dataInstance.first.pcrData.forEach((element) {
numList.add(element['pcr_count']);
numList2.add(element['date']);
});
newNumList = numList.map((e) => int.parse(e)).toList();
return Scaffold(
appBar: CustomAppBar(),
backgroundColor: Palette.primaryColor,
body: _isLoading
? Center(
child: CircularProgressIndicator(),
)
: CustomScrollView(
physics: ClampingScrollPhysics(),
slivers: [
_buildHeader(),
_buildRegionTabBar(),
_buildStateTabBar(),
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
sliver: SliverToBoxAdapter(
child: StatusGridViewer(local, today),
),
),
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
sliver: SliverToBoxAdapter(
child: CovidBarChart(
covidCases: newNumList.sublist(1, 8),
covidDates: numList2,
),
),
),
],
),
);
}
SliverPadding _buildHeader() {
return SliverPadding(
padding: const EdgeInsets.all(20.0),
sliver: SliverToBoxAdapter(
child: const Center(
child: const Text(
'Statistics',
style: const TextStyle(
color: Colors.white,
fontSize: 25.0,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
SliverToBoxAdapter _buildRegionTabBar() {
return SliverToBoxAdapter(
child: DefaultTabController(
length: 2,
child: Container(
margin: EdgeInsets.symmetric(horizontal: 20.0),
height: 50.0,
decoration: BoxDecoration(
color: Colors.white24,
borderRadius: BorderRadius.circular(25.0),
),
child: TabBar(
indicator: const BubbleTabIndicator(
tabBarIndicatorSize: TabBarIndicatorSize.tab,
indicatorHeight: 40.0,
indicatorColor: Colors.white60,
),
labelStyle: Styles.tabTextStyle,
labelColor: Colors.black,
unselectedLabelColor: Colors.white,
tabs: [
const Text('Local'),
const Text('Global'),
],
onTap: (index) {
print('Statmeaer changed ' + index.toString());
setState(() {
_local = !_local;
});
},
),
),
),
);
}
SliverPadding _buildStateTabBar() {
return SliverPadding(
padding: const EdgeInsets.all(20.0),
sliver: SliverToBoxAdapter(
child: DefaultTabController(
length: 2,
child: TabBar(
indicatorColor: Colors.transparent,
labelStyle: Styles.tabTextStyle,
labelColor: Colors.white,
unselectedLabelColor: Colors.white54,
tabs: [
const Text('Today'),
const Text('Total'),
],
onTap: (index) {
print('Date to show details selected ' + index.toString());
setState(() {
_today = !_today;
});
},
),
),
),
);
}
}
This is the covid properties class
class CovidProperties {
final String updateDate;
final int localNewCases;
final int localTotalCases;
final int totalHospitalized;
final int localDeaths;
final int localNewDeaths;
final int localRecovered;
final int localActiveCases;
final int globalNewCases;
final int globalTotalCases;
final int globalDeaths;
final int globalNewDeaths;
final int globalRecovered;
final int totalPCR;
final List<dynamics> pcrData;
CovidProperties(
{required this.updateDate,
required this.localNewCases,
required this.localTotalCases,
required this.totalHospitalized,
required this.localDeaths,
required this.localNewDeaths,
required this.localRecovered,
required this.localActiveCases,
required this.globalNewCases,
required this.globalTotalCases,
required this.globalDeaths,
required this.globalNewDeaths,
required this.globalRecovered,
required this.totalPCR,
required this.pcrData
});
}
Link to the API where I fetch Data
data-link
If you are fetching the data at the start of the widget I think that you should use a FutureBuilder in order to do that. In fact, you will avoid errors trying to access your fetched data in case you have not recieved yet the data from the API.
class _StatusScreenState extends State<StatusScreen> {
var _isLoading = false;
var _local = true;
var _today = true;
Future _initFuture;
get local {
return _local;
}
get today {
return _today;
}
#override
void initState() {
_initFuture = Provider.of<CovidData>(context, listen: false).fetchAndSetDataCovid();
super.initState();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
final covidDataProvider = Provider.of<CovidData>(context, listen: false);
//Move this logic to the provider
//final List<String> numList = [];
//final List<String> numList2 = [];
//final List<int> newNumList;
//dataInstance.first.pcrData.forEach((element) {
// numList.add(element['pcr_count']);
// numList2.add(element['date']);
//});
//newNumList = numList.map((e) => int.parse(e)).toList();
return Scaffold(
appBar: CustomAppBar(),
backgroundColor: Palette.primaryColor,
body: FutureBuilder(
future: _initFuture,
builder: (ctx, snapshot) {
if(snapshot.connectionState != ConnectionState.done)
return Center(
child: CircularProgressIndicator(),
)
else
return CustomScrollView(
physics: ClampingScrollPhysics(),
slivers: [
_buildHeader(),
_buildRegionTabBar(),
_buildStateTabBar(),
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
sliver: SliverToBoxAdapter(
child: StatusGridViewer(local, today),
),
),
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
sliver: SliverToBoxAdapter(
child: CovidBarChart(
covidCases: covidDataProvider.newNumList.sublist(1, 8),
covidDates: covidDataProvider.numList2,
),
),
),
],
),
);
}
I was able to Resolve this issue by moving the methods that I use to create the numList,NumList1, newNumList inside to my Providers fetchAndSetDataCovid method.
first I implemented List<int> pcrData and List<string> date separately and created this function to populate them. and after that Running the method inside of fetchAndSetCovidData() Method.
void getPcrCount() {
final List<String> pcrCount1 = [];
data.first.pcrData.forEach((element) {
pcrCount1.add(element['pcr_count']);
date.add(element['date']);
});
pcrCount = pcrCount1.map((e) => int.parse(e)).toList();
}

Flutter: Multiple Instances of Shared Preferences?

I am having a problem with SharedPreferences and multiple Modules I am generating on a Form using ListView.builder
The form is basically asking for some parents details and their childs details - by default the form assumes the parent has one child, but more can be added by clicking a button. The ChildModule has the ability to "close" but when "re-opened" the data doesn't persist, hence using SharedPreferences, it works fine with one Child, but once a second child is added it seems to be creating multiple Instances of SharedPreferences.
I have cut out everything to show what I am trying to achieve. NOTE this is being used as a Web App if it matters.
Oh and ChildModule needs to have its own state because it has a widget which requires it (not shown)
ENQUIRY FORM
final GlobalKey<FormBuilderState> _enquiryFormKey = GlobalKey<FormBuilderState>();
class EnquiryForm extends StatefulWidget {
static List<int> numberOfChildren = [1];
#override
_EnquiryFormState createState() => _EnquiryFormState();
}
class _EnquiryFormState extends State<EnquiryForm> {
int defaultNumberOfChildren = 1;
removeModule(){
setState(() {});
}
#override
Widget build(BuildContext context) {
return FormBuilder(
key: _enquiryFormKey,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: CustomTextField(
label: 'Parent First Name',
isRequired: true,
),
),
),
//ChildModuleList
ListView.builder(
shrinkWrap: true,
itemCount: EnquiryForm.numberOfChildren.length,
itemBuilder: (context,int index){
return ChildModule(EnquiryForm.numberOfChildren[index], removeModule);
}
),
SizedBox(height: 20,),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
ThemedButton(
onPressed: (){
setState(() {
defaultNumberOfChildren++;
EnquiryForm.numberOfChildren.add(defaultNumberOfChildren);
});
},
child: Text(
'Add Additional Child',
style: TextStyle(color: Colors.white),)
),
SizedBox(width: 10,),
ThemedButton(
onPressed: (){},
child: Text('Enquire Now', style: TextStyle(color: Colors.white),))
],
)
],
));
}
}
CHILD MODULE
class ChildModule extends StatefulWidget {
final int number;
final Function() callback;
ChildModule(this.number,this.callback);
#override
_ChildModule createState() => _ChildModule();
}
class _ChildModule extends State<ChildModule> {
SharedPreferences childModuleData;
String firstName;
bool loading = true;
bool isOpen;
#override
void initState() {
print('this module number is ${widget.number}');
_spInstance();
isOpen = true;
super.initState();
}
Future<void> _spInstance() async {
if(childModuleData == null && widget.number == 1) {
childModuleData = await SharedPreferences.getInstance();
print('got instance');
} else {
print('broken');
print(childModuleData);
};
String _testValue = childModuleData.getString('Child First Name');
if(_testValue == null){
childModuleData.setString('Child First Name', '');
loading = false;
} else {
childModuleData.clear();
_spInstance();
}
}
#override
Widget build(BuildContext context) {
return loading ? Loading() : Column(
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
if (isOpen == false) {
isOpen = true;
} else {
isOpen = false;
}
});
},
child: Container(
height: 40,
padding: EdgeInsets.only(left: 10, right: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
color: Colors.blue[50],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
'Child Details',
style: TextStyle(color: Colors.blue[300], fontSize: 16),
),
Row(
children: <Widget>[
isOpen
? Icon(
Icons.arrow_drop_down,
size: 30,
)
: Transform.rotate(
angle: math.pi / 2,
child: Icon(
Icons.arrow_drop_down,
size: 30,
),
),
widget.number > 1
? IconButton(icon: Icon(Icons.clear), onPressed: () async {
await FormFunctions().removeModule(widget.number, EnquiryForm.numberOfChildren);
widget.callback();
})
: Container(),
],
),
],
),
),
),
AnimatedContainer(
duration: Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
padding: EdgeInsets.fromLTRB(10, 5, 10, 5),
height: isOpen ? null : 0,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
CustomTextField(
label: fieldFirstName,
isRequired: true,
initalValue: childModuleData.getString(fieldFirstName) ?? '',
onChanged: (value){
childModuleData.setString(fieldFirstName, value);
},
),
],
),
],
),
),
],
);
}
}
CONSOLE ERROR AFTER SECOND MODULE IS CREATED
Launching lib/main.dart on Chrome in debug mode...
Syncing files to device Chrome...
Debug service listening on ws://127.0.0.1:58490/EkMAy9CGY74=
Debug service listening on ws://127.0.0.1:58490/EkMAy9CGY74=
this module number is 1
got instance
Instance of 'SharedPreferences'
null
this module number is 2 //Number 2 Module is created
broken
null
null
TypeError: Cannot read property 'getString' of null
at child_module._ChildModule.new._spInstance$ (http://localhost:58433/packages/webenrol/shared/widgets/day_button.dart.lib.js:3704:47)
at _spInstance$.next (<anonymous>)
at runBody (http://localhost:58433/dart_sdk.js:43121:34)
at Object._async [as async] (http://localhost:58433/dart_sdk.js:43149:7)
at child_module._ChildModule.new.[_spInstance] (http://localhost:58433/packages/webenrol/shared/widgets/day_button.dart.lib.js:3694:20)
at child_module._ChildModule.new.initState (http://localhost:58433/packages/webenrol/shared/widgets/day_button.dart.lib.js:3689:24)
at framework.StatefulElement.new.[_firstBuild] (http://localhost:58433/packages/flutter/src/widgets/widget_span.dart.lib.js:41219:58)
at framework.StatefulElement.new.mount (http://localhost:58433/packages/flutter/src/widgets/widget_span.dart.lib.js:12605:24)
at framework.SingleChildRenderObjectElement.new.inflateWidget (http://localhost:58433/packages/flutter/src/widgets/widget_span.dart.lib.js:11420:16)
in your case the concern is at the level of recording data in shared preferences. one solution would be to add the widget number in the key to save like this (await SharedPreferences.getInstance()).setString("Your Key${widgetNumber}");
and edit your function _spInstance
class ChildModule extends StatefulWidget {
final int number;
final Function() callback;
ChildModule(this.number,this.callback);
#override
_ChildModule createState() => _ChildModule();
}
class _ChildModule extends State<ChildModule> {
SharedPreferences childModuleData;
...
Future<void> _spInstance() async {
if(childModuleData == null) {
childModuleData = await SharedPreferences.getInstance();
print('got instance');
}
String _testValue = childModuleData.getString('Child First Name${widget.number}');
//NB: do not clear data on chlidModuleData any more.
...
}
...
}