passing arguments in flutter from one screen to another - flutter

hi guys im a newbie in flutter and i have this worker app with the basic crud operations, and i want to use the same screen for adding or updating a contact but i run into an error
return ListView(
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return GestureDetector(
onTap: () {
Navigator.of(context)
.pushNamed('/Add-contact-screen', arguments: data);
},
child: ListTile(
title: Text(data['name']),
),
);
}).toList(),
);
this is my contacts list screen where on tap on a contact i navigate to the add contact screen with the arguments the contact object containing all the contact data like name, email, number etc. so that the form can be populated with the data, and this is how i receive those arguments in the add contact screen
import 'package:flutter/material.dart';
import '../../services/firestore_service.dart';
import '../../models/contacts/contact.dart';
import '../../utils/helper_widgets.dart';
class AddContactScreen extends StatefulWidget {
AddContactScreen({Key? key}) : super(key: key);
static const routeName = '/Add-contact-screen';
#override
State<AddContactScreen> createState() => _AddContactScreenState();
}
class _AddContactScreenState extends State<AddContactScreen> {
String category = 'Intern';
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final db = FirebaseFirestore.instance;
final firestoreService = FirestoreService();
var editMode = true;
#override
Widget build(BuildContext context) {
final routeArgs = ModalRoute.of(context)?.settings?.arguments as Map<dynamic, dynamic>;
return Scaffold(
appBar: AppBar(
title: editMode ? const Text('New Contact') : const Text('Edit'),
),
body: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
children: [
addVerticalSpace(10),
DropdownButton(
value: category,
icon: const Icon(Icons.keyboard_arrow_down),
items: dropdownItems,
onChanged: (String? newValue) {
setState(() {
category = newValue!;
});
},
),
TextFormField(
initialValue: routeArgs['name'],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
},
child: const Text('Save'),
),
TextButton(
onPressed: () {
},
child: const Text('Edit'))
],
)
],
),
),
),
);
}
}
now this works fine the input field is populated with the value im getting from
ModalRoute.of(context)?.settings?.arguments as Map<dynamic, dynamic>;
and the contact is ready to be updated,
however when i click the add button to the contacts screen to open the add contact screen to add a brand new contact i get this error message
type 'Null' is not a subtype of type 'Map<dynamic, dynamic>' in type cast
clicking in the floating button or tapping the contact name navigates to the same page but doing with the button crashes the app i believe it has to be something about the arguments

as Map<dynamic, dynamic> making it non-nullable. You can do
final routeArgs =
ModalRoute.of(context)?.settings.arguments as Map<dynamic, dynamic>?;
Now you can do a null check before using routeArgs and those who accept null, you can do
initialValue: routeArgs?['name'],

Related

The function can't be unconditionally invoked

I am trying to build a dropdown that is filled with values from the database.
The error happens at
items: machineList[index].serienummer((String items)
The error I get is:
The function can't be unconditionally invoked because it can be null.
I tried to add items: machineList[index].serienummer! or items: machineList[index].serienummer? and items: machineList[index].serienummer?? parameters but doesn't seem to solve it. Any help would be great.
This is my dart file
class _MachinepageState extends State<Machinepage> {
_MachinepageState();
final ApiService api = ApiService();
late List<Machine> machineList;
String dropdownvalue = "";
#override initState(){
super.initState();
_getMachine();
machineList = [];
}
void _getMachine() async{
machineList = (await ApiService().getMoreMachine(widget.klant.klantId.toString()));
Future.delayed(const Duration(seconds: 1)).then((value) => setState(() {}));
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(title: const Text('Menu'),
backgroundColor:Colors.deepOrange
),
body: machineList == null || machineList.isEmpty
? const Center(
child: CircularProgressIndicator(),
)
: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget> [
ListView.builder(
shrinkWrap: true,
itemCount: machineList == null? 0 : machineList.length,
itemBuilder: (BuildContext context, int index) => Row(
children: [
Container(
margin: const EdgeInsets.only(top: 20.0, bottom: 25.0),
child: DropdownButton(
// Initial Value
value: machineList[1].serienummer,
// Down Arrow Icon
icon: const Icon(Icons.keyboard_arrow_down),
// Array list of items
items: machineList[index].serienummer((String items) {
return DropdownMenuItem(
value: items,
child: Text(items),
);
}).toList(),
// After selecting the desired option, it will
// change button value to selected value
onChanged: (String? newValue) {
setState(() {
dropdownvalue = newValue!;
});
},
),
)
],
)
),
My Machinemodel.dart
import 'dart:convert';
import 'package:flutter/src/material/dropdown.dart';
List<Machine> welcomeFromJson(String str) => List<Machine>.from(json.decode(str).map((x) => Machine.fromJson(x)));
String welcomeToJson(List<Machine> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class Machine {
Machine({
this.serienummerId,
this.serienummer,
this.bouwjaar,
this.urenstand,
this.locatie,
this.klantId,
});
int? serienummerId;
String? serienummer;
String? bouwjaar;
String? urenstand;
String? locatie;
String? klantId;
factory Machine.fromJson(Map<String, dynamic> json) => Machine(
serienummerId: json["SerienummerId"],
serienummer: json["Serienummer"],
bouwjaar: json["Bouwjaar"],
urenstand: json["Urenstand"],
locatie: json["Locatie"],
klantId: json["KlantId"],
);
Map<String, dynamic> toJson() => {
"SerienummerId": serienummerId,
"Serienummer": serienummer,
"Bouwjaar": bouwjaar,
"Urenstand": urenstand,
"Locatie": locatie,
"KlantId": klantId,
};
}
Since you passed the machineList[1].serienummer in DropdownButton value, I'm assuming that serienummer is not a list but some kind of value, i.e., an integer or string.
And you're trying to do this items: machineList[index].serienummer((String items) which will not work as items will only take lists as value.
So try this this:
items: machineList!.map((item) {
return DropdownMenuItem(
value: item.serienummer, //or whatever value you want to pass to the item
child: Text(item.serienummer.toString()),
);
})!.toList(),
Don't forget to put the null and empty check for machineList before passing it in DropDownButton items

How to add an item to a list on a separate page in Flutter

I am new to Flutter and currently building an app to log spasms that happens due to spasticity. This is somewhat like a ToDo style app in structure. So I have a list in my home.dart file that a ListViewBuilder to display my Spasm objects. What I want to do is to create a Spasm object in recordSpasm.dart and add it to the list in home.dart. How do I do that? I´ll post my code here:
home.dart
import 'package:flutter/material.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:spasmlogger/classses/spasm.dart';
class Home extends StatefulWidget {
const Home({ Key? key }) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
List<Spasm> Spasms = [
Spasm("Extremely strong", "Upper body", "Cannot control left arm"),
Spasm("Extremely strong", "Upper body", "Cannot control left arm")
];
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("SpasmLogger"),
actions: <Widget>[
IconButton(
icon: Icon(MdiIcons.help),
onPressed: () {
Navigator.pushNamed(context, '/about');
},
)
],
),
floatingActionButton: FloatingActionButton(
child: Icon(MdiIcons.plus),
onPressed: () {
Navigator.pushNamed(context, "/recordSpasm");
}
),
body: Padding(
padding: EdgeInsets.fromLTRB(16, 16, 16, 16),
child: ListView.builder(
itemCount: Spasms.length,
itemBuilder: (BuildContext context, int index){
return Card(
child: ListTile(
title: Text(Spasms[index].strength + " spasm detected in " + Spasms[index].bodyPart),
subtitle: Text(Spasms[index].comment)
)
);
},
)
),
);
}
}
recordSpasm.dart
import 'package:flutter/material.dart';
import 'package:spasmlogger/classses/spasm.dart';
class RecordSpasm extends StatefulWidget {
const RecordSpasm({ Key? key }) : super(key: key);
#override
_RecordSpasmState createState() => _RecordSpasmState();
}
class _RecordSpasmState extends State<RecordSpasm> {
#override
String Strength = "Regular strength";
List<String> Strengths = ["Regular strength", "Mildly stronger", "Severely Strong", "Extremely strong"];
String BodyPart = "Lower body";
List<String> BodyParts = ["Lower body", "Upper body", "Head"];
TextEditingController comment = new TextEditingController();
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Record spasm")
),
body:
Padding(
padding: EdgeInsets.all(16),
child: Form(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget> [
Text("Spasm strength"),
DropdownButton(
value: Strength,
items: Strengths.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? value) {
setState(() {
Strength = value!;
});
},
),
Text("Part of body"),
DropdownButton(
value: BodyPart,
items: BodyParts.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? value) {
setState(() {
BodyPart = value!;
});
},
),
Text("Comments"),
TextFormField(
maxLines: 5,
controller: comment,
),
ElevatedButton(
onPressed: () {
// Add object to the list in home.dart
print(Strength);
print(BodyPart);
print(comment.text);
},
child: Text("Record spasm")
)
]
)
)
)
);
}
}
Navigator.push returns a Future when the pushed page is popped. So you just need to add the Spasm object in the recordSpasm.dart:
ElevatedButton(
onPressed: () {
Navigator.pop(context,Spasm(Strength, BodyPart, comment.text));
},
child: Text("Record spasm")
)
and retrieve the object and "refresh" the page in the home.dart
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.pushNamed(context, "/recordSpasm").then((value) {
if (value != null && value is Spasm) {
setState(() {
// if this doesn't work add the value to the list then call setState
Spasms.add(value);
});
}
});
}),
Just a tip, in dart variable names should be lowercase (eg: String strength) :)
first you need to access to you list. you have 2 ways to do that
make the list static like this static List yourListName = [];
for the other way you don't need to do anything right now
so if you use way 1 then you can add something to your list like this:
import 'thePathFromTheFileThatHaveTheListIn';
...
// here we add something to your list without building the whole class again
TheClassWhereTheListWasIn.yourListName.add(...);
and if you use way 2 then you can add something to your list like this:
import 'thePathFromTheFielThatHaveTheListIn';
...
// here we add something to your list but here we build the whole class again
// and then add something to your list
TheClassWhereTheListIsIn().yourListName.add(...);

Nullable class instance with Provider

I have a class named User which basically has two variables. name and age. Also, I have another class named UserList.The aim of this class is to add the user objects to a list and return that list.
User model
class User with ChangeNotifier{
late String? name;
late int? age;
//set user name;
setName(String name){
this.name=name;
}
//set user age
setAge(int age){
this.age=age;
}
}
UserList class
class UserList with ChangeNotifier{
final List<User> _list=[];
//add a new user to the list.
void addUserToList(User user){
_list.add(user);
notifyListeners();
}
//return the private list of users.
List<User>getUsers() =>_list;
}
Here is the scenario. I want to insert a value from one page and view that inserted value on the second page. Look at the pictures below. On the first page,
I insert the age and name values
Assign those values in a User object instance (user.setName, user.setAge)
Add that user object instance in a UserList list
Use provider to provide that list.(Provider.of<UserList>(context).addUserToList(user);
First Page
First Page Code
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => UserList()),
ChangeNotifierProvider(create: (context) => User()),
],
child: const MyApp())
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
TextEditingController nameController = TextEditingController();
TextEditingController ageController = TextEditingController();
#override
Widget build(BuildContext context) {
User user =User();
return Scaffold(
backgroundColor: Colors.grey[300],
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: nameController,
decoration: const InputDecoration(
hintText: "Name",
),
),
TextField(
controller: ageController,
decoration: const InputDecoration(
hintText: "Age",
),
),
ElevatedButton(onPressed: (){
//providing userList
user.setName(nameController.text); //setting user name
user.setAge(int.parse(ageController.text)); // setting user age
Provider.of<UserList>(context,listen: false).addUserToList(user); //providing the list of users
nameController.text=""; //after insertion, clearing the text field
ageController.text="";//after insertion, clearing the text field
user=User(); // instantiating a new user object for the following insertion.
Navigator.push(context, MaterialPageRoute(builder: (context)=>const ViewUser()));
}, child: const Text("Submit"))
],
),
),
);
}
}
On the second page, display those inserted values in a ListView. Let's head to the main problem. Each item of ListViewis wrapped with a GestureDetector to be able to gain functionality.I hope everything is clear up to now.
The problem is that; When I click on each item I want to return to the first page. But, this time the TextField shouldn't be empty. It must be replaced with the value of that item. For exmple. If I click on John Nash and 43. The First page must pop up with field value of that list item. To be able to do that I use another Provider to provide User object. But, I couldn't do it because of null safety it gives an error about the user object is null. Is there any idea that could be helpful.
Second Page
Second Page code
class ViewUser extends StatelessWidget {
const ViewUser({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
List<User> userList = Provider.of<UserList>(context).getUsers();
return Scaffold(
appBar: AppBar(
title: const Text("Users List"),
),
body: ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: userList.length,
itemBuilder: (BuildContext context, int index) {
return Column(
children: [
const SizedBox(height: 15,),
GestureDetector(
onTap: (){
Provider.of<User>(context,listen: false)
.setName(userList[index].name!);
Provider.of<User>(context,listen: false)
.setAge(userList[index].age!);
Navigator.push(context, MaterialPageRoute(builder: (context)=>MyHomePage()));
},
child: Container(
height: 50,
color: Colors.amber,
child: Center(
child: Row(
children: [
Text("Name: " + userList[index].name!),
const SizedBox(
width: 40,
),
Text("Age:" + userList[index].age.toString()),
],
),
),
),
),
],
);
}),
);
}
}
While you are using Navigator.push you can pass user as arguments.
GestureDetector(
onTap: () {
final user = userList[index];
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MyHomePage(),
settings: RouteSettings(
arguments: user,
)),
);
},
MyHomePage build method will be
#override
Widget build(BuildContext context) {
User user = User();
User? args = ModalRoute.of(context)?.settings.arguments as User?;
if (args != null) {
nameController.text = args.name ?? "";
ageController.text = args.age.toString();
}
return Scaffold(....
You can also use Navigator.pop in this case.
GestureDetector(
onTap: () {
final user = userList[index];
Navigator.pop(context, user);
},
And to receive data on MyHomePage
onPressed: () async {
//....
final data = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ViewUser(),
),
);
if (data != null) {
nameController.text = data.name ?? "";
ageController.text = data.age.toString();
}
More about navigate-with-arguments.

Flutter with filter search page keeps adding same list view from query when returning to the page

I recently followed this answer : Listview filter search in Flutter on how to achieve a Flutter Listview with search filter . The filter works as explained in the answer . The problem each time i move of out of that particular page and then come back to it , then my results in the listview has doubled. When I go out of the page again , and come back again , it has tripled on so on .
I know I am supposed to clear the list somewhere when go out of the listview page , not sure where thou . Below the code from the example i followed . My MySQL Query is simply . SELECT * FROM table_name.
import 'dart:async';
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
void main() => runApp(new MaterialApp(
home: new HomePage(),
debugShowCheckedModeBanner: false,
));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
TextEditingController controller = new TextEditingController();
// Get json result and convert it to model. Then add
Future<Null> getUserDetails() async {
final response = await http.get(url);
final responseJson = json.decode(response.body);
setState(() {
for (Map user in responseJson) {
_userDetails.add(UserDetails.fromJson(user));
}
});
}
#override
void initState() {
super.initState();
getUserDetails();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Home'),
elevation: 0.0,
),
body: new Column(
children: <Widget>[
new Container(
color: Theme.of(context).primaryColor,
child: new Padding(
padding: const EdgeInsets.all(8.0),
child: new Card(
child: new ListTile(
leading: new Icon(Icons.search),
title: new TextField(
controller: controller,
decoration: new InputDecoration(
hintText: 'Search', border: InputBorder.none),
onChanged: onSearchTextChanged,
),
trailing: new IconButton(icon: new Icon(Icons.cancel), onPressed: () {
controller.clear();
onSearchTextChanged('');
},),
),
),
),
),
new Expanded(
child: _searchResult.length != 0 || controller.text.isNotEmpty
? new ListView.builder(
itemCount: _searchResult.length,
itemBuilder: (context, i) {
return new Card(
child: new ListTile(
leading: new CircleAvatar(backgroundImage: new NetworkImage(_searchResult[i].profileUrl,),),
title: new Text(_searchResult[i].firstName + ' ' + _searchResult[i].lastName),
),
margin: const EdgeInsets.all(0.0),
);
},
)
: new ListView.builder(
itemCount: _userDetails.length,
itemBuilder: (context, index) {
return new Card(
child: new ListTile(
leading: new CircleAvatar(backgroundImage: new NetworkImage(_userDetails[index].profileUrl,),),
title: new Text(_userDetails[index].firstName + ' ' + _userDetails[index].lastName),
),
margin: const EdgeInsets.all(0.0),
);
},
),
),
],
),
);
}
onSearchTextChanged(String text) async {
_searchResult.clear();
if (text.isEmpty) {
setState(() {});
return;
}
_userDetails.forEach((userDetail) {
if (userDetail.firstName.contains(text) || userDetail.lastName.contains(text))
_searchResult.add(userDetail);
});
setState(() {});
}
}
List<UserDetails> _searchResult = [];
List<UserDetails> _userDetails = [];
final String url = 'my url address for mysql php query ';
class UserDetails {
final int id;
final String firstName, lastName, profileUrl;
UserDetails({this.id, this.firstName, this.lastName, this.profileUrl = 'https://i.amz.mshcdn.com/3NbrfEiECotKyhcUhgPJHbrL7zM=/950x534/filters:quality(90)/2014%2F06%2F02%2Fc0%2Fzuckheadsho.a33d0.jpg'});
factory UserDetails.fromJson(Map<String, dynamic> json) {
return new UserDetails(
id: json['id'],
firstName: json['name'],
lastName: json['username'],
);
}
}
Try clearing the list before adding new values..
setState(() {
_userDetails.clear();
for (Map user in responseJson) {
_userDetails.add(UserDetails.fromJson(user));
}
});
Try adding this
#override
void dispose() {
_userDetails .clear();
_searchResult.clear();
super.dispose();
}

ArgumentError (Invalid argument: Instance of 'TextEditingController')

Please help.. i'm trying to make update data page, but this error come out in this line..
Firestore.instance.collection('reg').add({'name':controllerName})
here is the code:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class EditList extends StatefulWidget {
#override
_EditListState createState() => _EditListState();
}
class _EditListState extends State<EditList> {
TextEditingController controllerName;
#override
void initState() {
controllerName = new TextEditingController();
super.initState();
}
var name;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('Registration'),
backgroundColor: Colors.blue,
),
body: Container(
child: SingleChildScrollView(
padding: const EdgeInsets.all(30.0),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 20.0),
),
Text('GROUP'),
TextField(
controller: controllerName,
onChanged: (String str) {
setState(() {
name= str;
});
},
decoration: InputDecoration(
labelText: 'Name',
)),
//paste here
const SizedBox(height: 30),
RaisedButton(
onPressed: () {
if (controllerName.text.isNotEmpty) {
Firestore.instance.collection('reg').add({'name':controllerName})
.then((result){
Navigator.pop(context);
controllerName.clear();
}).catchError((err) =>print(err));
}
},
child: const Text('Submit', style: TextStyle(fontSize: 20)),
),
],
),
),
),
);
}
}
This line:
Firestore.instance.collection('reg').add({'name':controllerName})
should be replaced with:
Firestore.instance.collection('reg').add({'name':controllerName.text})
Also, you should probably give your TextField an initial value of an empty string so that it can't be null.
controllerName is not a String,
controllerName.text
use that
How do I add uuid inside the document? Tried adding .docs(uuid) before .add({ it's having an error.
CollectionReference users = FirebaseFirestore.instance.collection('users');
String? uuid = " ";
Future<void> addUser() {
FirebaseAuth.instance.authStateChanges().listen((User? user) {
if (user == null) {
print('User is currently signed out!');
} else {
uuid = user.uid;
print(uuid);
}
});
// Call the user's CollectionReference to add a new user
return users
.add({
'uuid': uuid, // John Doe
'first': firstNameController.text, // John Doe
'middle': middleNameController.text, // Stokes and Sons
'surname': surNameController.text // 42
})
.then((value) => print("User Added"))
.catchError((error) => print("Failed to add user: $error"));
}
Passing TextEditingController will definetly cause error because it just have instance of controller but you need text data to pass to function in upper most line. Controller attached to a textfield contains many of the property along with text inside the textfield.
You need to get the text from controller and pass it to the firebase function.
The line causing error:
Firestore.instance.collection('reg').add({'name':controllerName})
should be like this,
Firestore.instance.collection('reg').add({'name':controllerName.text})
and will work for sure.
Change this line
Firestore.instance.collection('reg').add({'name':controllerName})
To this line
Firestore.instance.collection('reg').add({'name':controllerName.text})
The difference is controllerName.text