This is my model:
class Foo {
final String name;
final String emailId;
Foo(this.name, this.emailId);
factory Foo.fromJson(Map<String, dynamic> json) {
return Foo(
json['name'] as String,
json['email_id'] as String,
);
}
}
I want to create its object by letting user enter text in the TextField, for that I decided to use Map.
Map<String, dynamic> map = {};
My first text field:
TextFormField(
onSaved: (s) => map['name'] = s,
)
and second:
TextFormField(
onSaved: (s) => map['email_id'] = s,
)
I'm creating an instance using:
final foo = Foo.fromJson(map);
Is there any better way of creating an object without modifying my model class (final fields as well as non-nullable fields) except for creating various fields to store the value and then calling Foo() constructor.
There is many ways to solve this, my recommendation is first to load the data, and then modify anything in the ui without touching the class.
First define your data:
// Example fetch fooFromJson(String)
final Foo foo = Foo(name: 'name', email: 'stack#overflow.com');
// Text controllers
late final TextEditingController _controllerName;
late final TextEditingController _controllerEmail;
// Form key
final formKey = GlobalKey<FormState>();
Define your #initState method:
#override
void initState() {
_controllerName = TextEditingController(text: foo.name);
_controllerEmail = TextEditingController(text: foo.email);
super.initState();
}
This way, the data will fetch from the backend just one time, and will show data to the UI without touching the class.
Then in your body widget:
return Form(
key: formKey,
child: ListView(
padding: const EdgeInsets.all(100),
shrinkWrap: true,
children: [
TextFormField(
controller: _controllerName,
//validator: myValidator,
),
TextFormField(
controller: _controllerEmail,
//validator: myValidator,
),
const SizedBox(height: 20),
ElevatedButton(child: const Text('Save'), onPressed: _onSaved),
],
),
);
When you click save button, set the data to your class again (if you don't want to put the data again to the class go to the end of the answer):
_onSaved() {
if (formKey.currentState!.validate()) {
foo.name = _controllerName.text;
foo.email = _controllerEmail.text;
}
}
Result:
On the other hand, Form is use to validate data, for example you want "Text should be 10 characters maximum":
TextFormField(
controller: _controllerName,
validator: _nameValidator,
autovalidateMode: AutovalidateMode.onUserInteraction,
)
And the function:
String? _nameValidator(String? text) {
return text!.length < 10 ? 'Name should have 10 characters at least' : null;
}
Result when you click save:
The data will not be saved until validation is correct.
Also if you want to fetch data from the internet or database, I recommend to improve your data class, for example you have the following Json:
{
"name" :"",
"email" : ""
}
The class should be:
import 'dart:convert';
Foo fooFromJson(String str) => Foo.fromJson(json.decode(str));
String fooToJson(Foo data) => json.encode(data.toJson());
class Foo {
Foo({
this.name,
this.email,
});
String name;
String email;
factory Foo.fromJson(Map<String, dynamic> json) => Foo(
name: json["name"],
email: json["email"],
);
Map<String, dynamic> toJson() => {
"name": name,
"email": email,
};
}
So you can POST foo.toJson() to whatever API you have.
This is a good tool that can help you with that https://app.quicktype.io/
Related
I have this Account class
import 'package:project/models/category_model.dart';
enum AccountTypes {
cash,
banks,
}
class Account {
AccountTypes type;
double value;
List<BalnceCategory>? categories;
Account({
required this.type,
required this.value,
this.categories,
});
Map<String, dynamic> toJSON() {
return {
"type": type,
"value": value,
"categories": categories,
};
}
}
Map<AccountTypes, List<dynamic>> accounts = {
AccountTypes.cash: [
BalnceCategory(image: "food.png", title: "Food", value: 412.5).toJSON(),
BalnceCategory(image: "shopping.png", title: "Shopping", value: 412.5).toJSON(),
],
AccountTypes.banks: [
BalnceCategory(image: "food.png", title: "Food", value: 1242.63).toJSON(),
BalnceCategory(image: "shopping.png", title: "Shopping", value: 1242.63).toJSON(),
]
};
each Account should contain a list of BalnceCategory
class BalnceCategory {
String image;
String title;
double value;
BalnceCategory({
required this.image,
required this.title,
required this.value,
});
Map<String, dynamic> toJSON() {
return {
"image": image,
"title": title,
"value": value,
};
}
}
Now I want to display this Map Map<AccountTypes, List<dynamic>> accounts in two sections...I will refer to this map as accounts.
So in the first section I want to list all available accounts in something like a Row with a button for each account, so what I did is I mapped through accounts like this accounts.entries.map and returned a button for each account, and these buttons can set a state called currentIndex with it's index.
Now in the second section I want to list all accounts categories depending on the currentIndex state value, so for example if the currentIndex value is 0 I want to display all the categories in cash account, and if the currentIndex value is 1 I want to display all the categories in banks account.
So far all I am done the buttons section and I it is working properly and my problem is in the second section. I tried to do this
Expanded(
child: GridView.builder(
physics: const BouncingScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: mainUnit / 2,
crossAxisSpacing: mainUnit / 2,
childAspectRatio: 3 / 4,
),
itemCount: accounts.keys.length,
itemBuilder: (context, index) {
return accounts.forEach((key, value) {
if (key.index == currentIndex) {
value.map((e) => {Text(e.toString())});
}
});
},
),
),
but it gives me this error: The return type 'void' isn't a 'Widget', as required by the closure's context.
The ItemBuilder should return a widget, you return accounts.forEach(...) that is a void function ( forEach() is a void function/closure).
Try this:
Text( accounts.keys.firstWhere( (item) => item.index == currentIndex,
orElse: ()=> "",).toString() );
BUT!!!!
Wait a moment!!!!!
Why don't you take a look at
https://pub.dev/packages/flutter_sticky_header
You should implement what you need: it displays an header that, in your case, could be a string AccountTypes.cash.toString() or AccountTypes.banks.toString(), and then follow the example and you should obtain what you need ( display grouped cards for AccountTypes ). more over I suggest you to use
https://pub.dev/packages/freezed
It helps you to defined your data class, and to Serialize then to JSON.
For Example:
import 'package:freezed_annotation/freezed_annotation.dart';
part 'account.g.dart';
part 'account.freezed.dart';
#freezed()
class Account with _$Account {
factory Account.cash({
#Default(0) double? value;
List<BalnceCategory>? categories;
}) = CashAccount;
factory Account.bank({
#Default(0) double? value;
List<BalnceCategory>? categories;
}) = BankAccount;
factory Account.fromJson(Map<String, dynamic> json) =>
_$AccountFromJson(json);
}
In that manner you have your data class ( account ) with its type ( cash / bank ) and serializable.
To create an account it is easy :
var myCash = Account.cash(... , ... );
Both CashAccount and BankAccount are two different classes that implement Account ( abstract class ), you can use it in a simple list of Accounts.
then , to chack for bank/cash you can use:
var myAccount = Account.cash(....);
myAccount.when(
bank: (BankAccount account) => ....,
cash: (CashAccount account) => .... ,
),
So i have my dart call to my api get method. Btw the way am just learning flutter and dart and trying out basic crud operations I would use to be doing in .net and c#
import 'dart:convert';
import 'package:theapp/models/Players.dart';
import 'package:http/http.dart';
class ApiService {
final String apiUrl = "https://apiurlhidden.com/api";
final String getAllPlayersEndPoint = "/GetAllPlayers/";
Future<List<Player>> getAllPlayers() async {
final getallPlayersUrl = Uri.parse(apiUrl + getAllPlayersEndPoint);
Response res = await get(getallPlayersUrl);
if (res.statusCode == 200) {
List<dynamic> body = jsonDecode(res.body);
List<Player> players =
body.map((dynamic item) => Player.fromJson(item)).toList();
return players;
} else {
throw "Failed to load cases list";
}
}
}
And I have my listview here but it complaining saying key and players do not exist
import 'package:flutter/material.dart';
import 'package:theapp/models/Players.dart';
class PlayerList extends StatelessWidget {
List<Player> players = [];
PlayerList({Key key, this.players}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: players == null ? 0 : players.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: InkWell(
onTap: () {},
child: ListTile(
leading: Icon(Icons.person),
title: Text(players[index].firstName),
subtitle: Text(players[index].surname.toString()),
),
));
});
}
}
My Model
class Player {
final int id;
final int type;
final String playerLevel;
final String firstName;
final String surname;
Player(this.id, this.type, this.playerLevel, this.firstName, this.surname);
factory Player.fromJson(Map<String, dynamic> json) {
return Player(
json['id'],
json['type'],
json['playerlevel'],
json['firstname'],
json['surname'],
);
}
#override
String toString() =>
'Players{id: $id, firstName: $firstName, lastName: $surname}';
}
Is there any reason why it should not recognize players and key in my list view page also how do I get the items to appear in the listview.
Picture only added to show the context in the items I mentioned above. Also coming from a .net background I would normally use an observable collection so it gets any changes in data in real-time am I using the correct approach for that.
Use required keyword to make parameters mandatory.
PlayerList({required Key key, required this.players}) : super(key: key);
Named parameters are optional unless they’re explicitly marked as required.
See Parameters for details.
I'm building an application for the company I'm working for in Flutter. We are using the MVVM (Model, View, ViewModel) architecture with the other developer I'm working with.
I would like to display user data from my ViewModel to my edit form (those data are fetched through our API).
The problem is: the data won't display to my view. I have access to it and I can print it (see screenshots below)...
What I tried so far :
I used initialValue primarily and called, for instance, my 'lastName' variable (but it doesn't show anything)
I tried using a controller for each field. With this method, it shows my user data but I then have a weird keyboard issue where each time I want to type some content, the cursor just goes to the start and deletes the word.
Also, I noticed that my variable can be displayed in a Text() widget.
I'm pretty clueless and I would really love to get an answer on this bug.
class MyAccountViewModel with ChangeNotifier {
String _lastName;
MyAccountViewModel() {
// this._lastName = 'Hardcoded text';
ApiHelper api = new ApiHelper();
api.getUserData().then((Map<String, dynamic>response) {
print(response);
this._lastName = response['last_name'];
});
notifyListeners();
}
String get lastName => this._lastName;
set lastName(String value) {
this._lastName = value;
notifyListeners();
}
Widget editProfileForm(model, BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
initialValue: model.lastName,
),
],
),
);
}
current view
response after the API call
Thanks to the answer I received in this post, I managed to find a working solution.
As advised in the previous comments, I needed to instantiate a controller and bind for instance "lastName" from my api response to controller.text.
Here is a sample code using the MVVM architecture :
class MyAccountViewModel with ChangeNotifier {
TextEditingController _lastNameController;
MyAccountViewModel() {
_lastNameController = new TextEditingController();
ApiHelper api = new ApiHelper();
api.getUserData().then((Map<String, dynamic> response) {
this._lastNameController.text = response['last_name'];
notifyListeners();
});
}
TextEditingController get lastName => this._lastNameController;
set lastName(TextEditingController value) {
this._lastNameController = value;
notifyListeners();
}
}
Widget editProfileForm(model, BuildContext context) {
return Form(
key: _formKey,
child: TextFormField(
controller: model.lastName,
),
);
}
#Metr0me, have you tried using controller to update the value? It could look something like this,
Initialize the controller as final lastNameController = TextEditingController();
Assign text value to the controller when you have your model instance as,
MyAccountViewModel model = new MyAccount.....
lastNameController.text = model.lastName;
setState(() {}); //Refresh if you need to
Assign lastNameController to your form field as,
child: Column(
children: <Widget>[
TextFormField(
controller: lastNameController,
),
],
)
Set the data to the text field
setState (() {
lastNameController.text = model.lastName ;
});
Assign Controller to your textform field
TextFormField(
controller: lastNameController,
),
How i can build dropdown with 1 template Dropdown with 2 different List Item.
iam very confused.
i have 1 class Dropdown but i want 2 different List just using 1 class Dropdown
I hope you understand what I mean
DropdownButton<MenuItem>(
isExpanded: true,
icon: Icon(Icons.keyboard_arrow_down),
value: dropdownValue,
onChanged: (MenuItem newValue) {
setState(() {
dropdownValue = newValue;
});
},
items: items.map<DropdownMenuItem<MenuItem>>((MenuItem value) {
return DropdownMenuItem<MenuItem>(
value: value,
child: Text(value.name),
);
}).toList());
}
class MenuItem {
final int id;
final String name;
const MenuItem(this.id, this.name);
}
const List<MenuItem> items = [
MenuItem(1, 'Facebook'),
MenuItem(2, 'Instagram'),
MenuItem(3, 'THREE'),
MenuItem(4, 'FOUR'),
];
What you need to do is, merging different objects via abstraction. Now we have MenuItem abstract class and we use it inside the widget clas, because we need a common Class to merge different objects. Since they have common String field to show inside the DropdownMenu, it's easy to how to merge. No problem there.
Also this means:
[...items1, ...items2]
We are creating a new list combined by two other lists.
This question is more about OOP rather than Flutter. Try to make some practise for Abstraction you'll figure out.
Our DropdownMenu value variable under the State class:
class _DropDownTestState extends State<DropDownTest> {
MenuItem value;
And the solution, this is the DropDown widget:
DropdownButton<MenuItem>(
isExpanded: true,
icon: Icon(Icons.keyboard_arrow_down),
value: value,
onChanged: (MenuItem newValue) {
setState(() {
value = newValue;
});
},
items: <MenuItem>[...items1, ...items2]
.map<DropdownMenuItem<MenuItem>>((MenuItem value) {
return DropdownMenuItem<MenuItem>(
value: value,
child: Text(value.name),
);
}).toList())
These are the Class of different objects connected with one abstract class:
abstract class MenuItem {
final String name;
const MenuItem(this.name);
}
class MenuItem1 extends MenuItem {
final int id;
final String name;
const MenuItem1(this.id, this.name) : super(name);
}
class MenuItem2 extends MenuItem {
final String name;
final double price;
const MenuItem2(this.price, this.name) : super(name);
}
Lists:
const List<MenuItem1> items1 = [
MenuItem1(1, 'ONE'),
MenuItem1(2, 'TWO'),
MenuItem1(3, 'THREE'),
];
const List<MenuItem2> items2 = [
MenuItem2(10, 'FOO'),
MenuItem2(50, 'BAR'),
MenuItem2(90, 'BAZ'),
];
I was building a DataTable from json call. Everything is going well but data is not showing.
//standing.dart
import '../modal/catelog_point_table_model.dart';
import '../services/category_point_table_services.dart';
import 'package:flutter/material.dart';
class DataTableWidget extends StatefulWidget {
#override
DataTableWidgetState createState() => DataTableWidgetState();
}
class DataTableWidgetState extends State<DataTableWidget> {
final List<String> cityColumns = [
'Team',
'M',
'W',
'NRR',
'Pts'
];
List<City> cities;
bool ascending;
#override
void initState() {
super.initState();
ascending = false;
}
#override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: width*1.5,
child: ListView(
children: <Widget>[
buildDataTable(),
],
),
),
);
}
Widget buildDataTable() => DataTable(
sortAscending: ascending,
columns: cityColumns
.map(
(String column) => DataColumn(
label: Text(column),
onSort: (int columnIndex, bool ascending) => onSortColumn(
columnIndex: columnIndex, ascending: ascending),
),
)
.toList(),
rows: cities.map((City city) => DataRow(
cells: [
DataCell(Text('${city.title}')),
DataCell(Text('${city.price}')),
DataCell(Text('${city.description}')),
DataCell(Text('${city.nrr}')),
DataCell(Text('${city.pts}')),
],
))
.toList(),
);
void onSortColumn({int columnIndex, bool ascending}) {
if (columnIndex == 0) {
setState(() {
if (ascending) {
cities.sort((a, b) => a.title.compareTo(b.title));
} else {
cities.sort((a, b) => b.title.compareTo(a.title));
}
this.ascending = ascending;
});
}
}
}
//catelog_point_table_model.dart
import 'dart:async' show Future;
import '../modal/catelog_point_table_model.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
Future<List<City>> loadCatelog(String id) async {
String url = 'https://icc-point-table.nittodigital.now.sh/api/Catelogs/category/5ce1a425eda9891fa8b51430';
final response = await http.get(url);
print(response.body);
return cities(response.body);
}
//catelog_point_table_model.dart
import 'dart:convert';
class City {
final int imgcount;
final String id;
final String title;
final String price;
final String description;
final String nrr;
final String pts;
final List<String> gallery;
City({
this.imgcount,
this.id,
this.title,
this.price,
this.description,
this.nrr,
this.pts,
this.gallery
});
factory City.fromJson(Map<String, dynamic> parsedJson) {
var streetsFromJson = parsedJson['gallery'];
//print(streetsFromJson.runtimeType);
// List<String> streetsList = new List<String>.from(streetsFromJson);
List<String> galleryList = streetsFromJson.cast<String>();
return new City(
imgcount: parsedJson['img_v'],
id: parsedJson['_id'],
title: parsedJson['title'],
price: parsedJson['price'],
description: parsedJson['description'],
nrr: parsedJson['nrr'],
pts: parsedJson['pts'],
gallery: galleryList,
);
}
}
List<City> cities(str) {
final jsonData = json.decode(str);
return List<City>.from(jsonData.map((x) => City.fromJson(x)));
}
NoSuchMethodError: The method 'map' was called on null
This is what I am getting. Maybe I am not getting data because List is not static type.
it would be really nice if someone told me how to fetch the data correctly and make a bond with DataRow.
*sorry for my poor English.
You have to call your loadCatelog from inside your initState function. Since your call is async and initState does not allow async calls, you have to put the code in a seperate method:
#override
void initState() {
super.initState();
ascending = false;
_getData();
}
void _getData() async{
cities = await loadCatelog();
}
Also, you are asking for an id as a parameter for your function, but never use it. So either remove that parameter or pass it accordingly.
There would be an even more elegant way by using FutureBuilder if you only need to retreive the data from the API once, or StreamBuilder if you want to get live updates from said API.