How can I use List<String> in a for loop to both generate column name AND value of that column? - flutter

I have a Flutter app that gets data from an SQLite database. That database has a table with a huge number of columns, so I've written a function that puts those column names into a list.
Therefore, columnsList contains list of the columns in my database table.
How can I use columnsList to BOTH display the name of the column AND the VALUE of that column in a for loop?
Pseudocode
List<String> columnsList = [];
columnsList = ['RecordId', 'FirstName', 'MiddleName', 'LastName', 'Suffix', 'StreetNumber', 'StreetName'];
fetchRecord(1); // Inside initState() - returns values shown in example output
for (var i in columnsList) ...[
Text("$i: <<<VALUE OF i>>>"), // Here's where I need help
],
fetchRecord
Fetch Record looks something like this with a huge list of variables that I'd like to replace with the list.
//--- Fetch Record ---
bool isLoading = true;
List<Map<String, dynamic>> myRecord = [];
void fetchRecord(int recordId) async {
final data = await SQLiteMethods.getRecord(recordId);
setState(() {
myRecord = data;
firstName = myRecord[0]["FirstName"].toString() ?? '';
middleName = myRecord[0]["MiddleName"].toString() ?? '';
lastName = myRecord[0]["LastName"].toString() ?? '';
suffix = myRecord[0]["Suffix"].toString() ?? '';
streetNumber = myRecord[0]["StreetNumber"].toString() ?? '';
streetName = myRecord[0]["StreetName"].toString() ?? '';
isLoading = false;
});
}
Example Output
RecordId: 1
FirstName: Bob
MiddleName: E
LastName: Jones
Suffix: Mr
StreetNumber: 123
StreetName: Main St
Thanks for your help!

I tried to display your items from SQLiteMethods.getRecord with recordId from Widget.
I hope this helps.
class YourWidget extends StatefulWidget {
const YourWidget({Key? key, required this.recordId}) : super(key: key);
final int recordId;
#override
State<YourWidget> createState() => _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
List<Map<String, dynamic>> myRecord = [];
final List<String> columnsList = [
'RecordId',
'FirstName',
'MiddleName',
'LastName',
'Suffix',
'StreetNumber',
'StreetName'
];
bool isLoading = false;
#override
void initState() {
super.initState();
fetRecord(widget.recordId);
}
Future<void> fetRecord(int recordId) async {
isLoading = true;
myRecord = await SQLiteMethods.getRecord(recordId);
isLoading = false;
setState(() {});
}
#override
Widget build(BuildContext context) {
if (isLoading) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Column(children: [
for (final record in myRecord)
Container(
color: Colors.black12,
padding: const EdgeInsets.all(10),
child: Column(
children: [for (final key in columnsList) Text('$key: ${record[key]}')],
),
)
]);
}
If you are getting Map from SQLiteMethods.getRecord, then you should do two changes,
List<Map<String, dynamic>> myRecord = []; to Map<String, dynamic> myRecord = {};.
Remove the higher level Column and lower level should be for (final key in columnsList) Text('$key: ${myRecord[key]}')

use this:
Column(children: [for (var i in columnsList) Text("twxt: ${i}")])

Related

flutter: Unhandled Exception: type 'Null' is not a subtype of type 'String'

I tried to use Moralis API to call NFT via wallet address, but got following error.
Unhandled Exception: type 'Null' is not a subtype of type 'String'
These are the code lines that were pointed out as having errors.
final meta = jsonDecode(map?['metadata']);
meta_name = meta['name'] ?? '';
meta_image = meta['image'] ?? '';
meta_description = meta['description'] ?? '';
if (response.statusCode == 200) {
nfts = jsonDecode(response.body)['result'].map<Nfts>((result) {
return Nfts.fromMap(result);
}).toList();
}
The full code for the first one is below.
import 'dart:convert';
class Nfts {
late String total;
late String page;
late String page_size;
late String cursor;
late String result;
late String token_id;
late String token_address;
late String amount;
late String owner_of;
late String token_hash;
late String block_number_minted;
late String block_number;
late String contract_type;
late String name;
late String symbol;
late String token_uri;
late String metadata;
late String last_token_uri_sync;
late String last_metadata_sync;
late String minter_address;
late String meta_name;
late String meta_image;
late String meta_description;
Nfts({
required this.total,
required this.page,
required this.page_size,
required this.cursor,
required this.result,
required this.token_id,
required this.token_address,
required this.amount,
required this.owner_of,
required this.token_hash,
required this.block_number_minted,
required this.block_number,
required this.contract_type,
required this.name,
required this.symbol,
required this.token_uri,
required this.metadata,
required this.last_token_uri_sync,
required this.last_metadata_sync,
required this.minter_address,
required this.meta_name,
required this.meta_image,
required this.meta_description,
});
Nfts.fromMap(Map<String, dynamic>? map) {
total = map?['total'] ?? '';
page = map?['page'] ?? '';
page_size = map?['page_size'] ?? '';
cursor = map?['cursor'] ?? '';
result = map?['result'] ?? '';
token_id = map?['token_id'] ?? '';
token_address = map?['token_address'] ?? '';
amount = map?['amount'] ?? '';
owner_of = map?['owner_of'] ?? '';
token_hash = map?['token_hash'] ?? '';
block_number_minted = map?['block_number_minted'] ?? '';
block_number = map?['block_number'] ?? '';
contract_type = map?['contract_type'] ?? '';
name = map?['name'] ?? '';
symbol = map?['symbol'] ?? '';
token_uri = map?['token_uri'] ?? '';
metadata = map?['metadata'] ?? '';
last_token_uri_sync = map?['last_token_uri_sync'] ?? '';
last_metadata_sync = map?['last_metadata_sync'] ?? '';
minter_address = map?['minter_address'] ?? '';
final meta = jsonDecode(map?['metadata']);
meta_name = meta['name'] ?? '';
meta_image = meta['image'] ?? '';
meta_description = meta['description'] ?? '';
}
}
The full code for the second one is below.
class NftsProviders{
Uri uri = Uri.parse('https://deep-index.moralis.io/api/v2/(personal metamask wallet address)/nft?chain=polygon&format=decimal');
Future<List<Nfts>> getNfts() async {
List<Nfts> nfts = [];
final response = await http.get(uri, headers: {
'accept': 'application/json',
'X-API-Key' : 'o1g9ywaRjZvZaeaByxhZc7mFOBVVvDJEksU0jeZ8b34fNX03ISTc72fltfsAnuYG'
});
if (response.statusCode == 200) {
nfts = jsonDecode(response.body)['result'].map<Nfts>((result) {
return Nfts.fromMap(result);
}).toList();
}
else {
throw Exception('Failed to load NFT');
}
return nfts;
}
}
With this API, I tried to create a Gridview.builder.
class NftsScreen extends StatefulWidget {
#override
_NftsScreenState createState() {
return new _NftsScreenState();
}
}
class _NftsScreenState extends State<NftsScreen> {
List<Nfts> nfts = [];
bool isLoading = true;
NftsProviders nftsProvider = NftsProviders();
Future initNfts() async {
nfts = await nftsProvider.getNfts();
}
#override
void initState() {
super.initState();
initNfts().then((_) {
setState(() {
isLoading = false;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("nfts http"),
),
body: isLoading
? Center(
child: const CircularProgressIndicator(),
)
: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.5,
crossAxisSpacing: 20,
mainAxisSpacing: 20),
itemCount: nfts.length,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.all(8.0),
child: Column(
children: [
// Text(nfts[index].name),
// Text(nfts[index].metadata),
CupertinoButton(
onPressed: () {},
// CircleAvatar with NetworkImage(nfts[index].meta_image)
// size of 100, 100
child: CircleAvatar(
radius: 100,
backgroundImage: NetworkImage(nfts[index].meta_image,),
)
)
],
),
);
}),
);
}
}
I want to request NFT information according to the user's wallet address called from Firebase.
Thanks in advance.
This is part of the api call response.
{
"total": null,
"page": 1,
"page_size": 100,
"cursor": null,
"result": [
{
"token_address": "0x53a0018f919bde9c254bda697966c5f448ffddcb",
"token_id": "46388765668907266497641806581710410401632846941109288029645926940148068689172",
"owner_of": "0xe3281571a136c11cc66d225902d494d29aaf7cb9",
"block_number": "30362645",
"block_number_minted": "30362645",
"token_hash": "8b025de30055bd161b2774da64fc283a",
"amount": "1",
"contract_type": "ERC721",
"name": "EDNS",
"symbol": "EDNS",
"token_uri": "https://api.edns.domains/metadata/0x53a0018f919bde9c254bda697966c5f448ffddcb/46388765668907266497641806581710410401632846941109288029645926940148068689172/metadata.json",
"metadata": "{\"name\":\"goyangtwo.meta\",\"description\":\"Domain goyangtwo.meta from EDNS Domains\",\"image\":\"https://api.edns.domains/metadata/0x53a0018f919bde9C254bda697966C5f448ffDDcB/46388765668907266497641806581710410401632846941109288029645926940148068689172/image.svg\",\"attributes\":[{\"trait_type\":\"TLD\",\"value\":\"meta\"}]}",
"last_token_uri_sync": "2022-12-06T14:08:39.924Z",
"last_metadata_sync": "2022-12-06T14:08:44.789Z",
"minter_address": "0x805ec22fca66eca02e244689b47fc2f180a94f01"
}
],
"status": "SYNCED"
}
can you share the response of the api call, most probably there will be a typo in the JSON decoding
meta_name = meta['name'] ?? '';
meta_image = meta['image'] ?? '';
meta_description = meta['description'] ?? '';
try quicktype.io to generate response models
What's the source of Nfts? Do you not have accidently defined a field like cursor or total as String instead of String? Otherwise, please provide the code for Nfts as it might have fields of type String without defaults that are not present in the json, causing the Nfts.fromMap(result); to fail.
As a general tip, it is wise to NOT assume that this will always succeed. In case it fails, then i.e. return null instead of crashing your app.
Edit added solution:
In the function
Nfts.fromMap(Map<String, dynamic>? map) {}
the variable map will be the full map (with total, page, result, etc. entries) which does not contain a metadata tag, so...
final meta = jsonDecode(map?['metadata']);
will be the same as
final meta = jsonDecode(null);
which is the reason for the error message. A string is expected!
However, you already jsonDecode (d) the response, so why do it again, because you already have a map of the response :)
You just need to pick the right tags from the map.
final result = map?['result'];
final metadata = result?['metadata'];
meta_name = metadata?['name'] ?? '';
meta_image = metadata?['image'] ?? '';
meta_description = metadata?['description'] ?? '';
final result = map?['result'];
final metadata = result?['metadata'];
meta_name = metadata?['name'] as String? ?? '';
meta_image = metadata?['image'] as String? ?? '';
meta_description = metadata?['description'] as String? ?? '';

Flutter shared_preferences save list<any_class>

I tried to save a list to my shared preferences but the list isn't a String list it is a list of a special type class "todo". I tried it with ".cast<Todo>();", this worked with prefs.getStringList.... but dont work with prefs.setStringList.
Here is a screenshot:
When I try to do prefs.setStringList("shoppingCard", _todos); it says: "The argument type 'List' can't be assigned to the parameter type 'List'."
This is the source code of the class todo:
class Todo {
Todo({required this.name, required this.checked});
final String name;
bool checked;
}
class TodoItem extends StatelessWidget {
TodoItem({
required this.todo,
required this.onTap,
}) : super(key: ObjectKey(todo));
final Todo todo;
final Function onTap;
TextStyle? _getTextStyle(bool checked) {
if (!checked) return null;
return const TextStyle(
color: Colors.black54,
decoration: TextDecoration.lineThrough,
);
}
#override
Widget build(BuildContext context) {
return ListTile(
onTap: () {
onTap(todo);
},
leading: CircleAvatar(
child: Text(todo.name[0]),
),
title: Text(todo.name, style: _getTextStyle(todo.checked)),
);
}
}
If you need to save list of custom class you need convert it to string. First change your class model to this:
class Todo {
Todo({required this.name, required this.checked});
final String name;
bool checked;
static Todo fromJson(Map<String, dynamic> json) {
return Todo(name: json['name'], checked: json['checked']);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'checked': checked,
};
}
}
then for saving your list in SharedPreferences, try this:
var prefs = await SharedPreferences.getInstance();
prefs.setString('shoppingCard',
jsonEncode({'data': _todos.map((e) => e.toJson()).toList()}));
and for getting it, try this:
var prefs = await SharedPreferences.getInstance();
String? str = prefs.getString('shoppingCard');
if (str != null) {
var result = (jsonDecode(str) as Map)['data'] as List;
result.map((e) => Todo.fromJson(e)).toList();
}
for example in you case, lets say we have list below:
List<Todo> _todos = [
Todo(checked: false, name: 'test1'),
Todo(checked: true, name: 'test2')
];
we add this list to SharedPreferences, like this:
Future<void> _addTodoItem(String name) async {
var prefs = await SharedPreferences.getInstance();
prefs.setString('shoppingCard',
jsonEncode({'data': _todos.map((e) => e.toJson()).toList()}));
_textFieldController.clear();
}
and we get list from SharedPreferences, like this:
Future<void> _getodoItem(String name) async {
var prefs = await SharedPreferences.getInstance();
var value = prefs.getString('shoppingCard');
if (value != null) {
var result = (jsonDecode(value) as Map)['data'] as List;
setState(() {
_todos = result.map((e) => Todo.fromJson(e)).toList();
});
}
}

Understanding and implementing OOP (in flutter/dart) to abstract shared structure and functionality

(Edited to clarify & update with progress)
When building an app with various lists of items and some cross-referencing between them; e.g. I have a stock class and a StockSet that extends ChangeNotifier as well as holds some basic rest functionality etc. But I'm also going to have Clients, Meetings etc, all of which are basically the same except for minor differences in fields.
So I recon I should define a parent class, say Item and ItemSet that, and then the actual elements in my app will extend those. - Or is that over-engineering?
However, in say my current StockSet I have methods to fetch either a single, or a number of stock items, i.e. ...Future<Stock> _fetchAStock() async {...
the only significant difference between it and say client records will be the REST url, and type of object returned (and give or take a few fields).
Is it better to have individual methods in all the child level classes - seems simpler though more code = more potential errors etc. OR build the fetch functionality into the parent class with some kind of abstraction/specification/parameterization(/or external configuration look-up) of the case specific differences?
New-ish to OOP and Flutter/Dart and it seems clearer some days than others... this is an others :-)
What I have:
class StocksSetRoute extends StatelessWidget with asc_alertBar {
const StocksSetRoute({Key? key}) : super(key: key);
static const indexStr = 'stocks';
static const labelStr = 'Properties';
static GlobalKey myKey = GlobalKey();
static GlobalKey parentKey = GlobalKey();
#override
Widget build(BuildContext context) {
var stockSet = context.watch<StockSet>();
return Scaffold(
key: parentKey,
appBar: AppBar(
title: const TitleRow(indexStr: indexStr, labelStr: labelStr),
),
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// these will be filters, order toggle etc.
children: [
ElevatedButton(
onPressed: (stockSet.isRemoteEmpty)
? null
: () async {
try {
await stockSet.fetch(5);
} catch (e) {
alertBar(
'Could not fetch any more. ', stockSet as ItemSet,
backgroundColor: Colors.grey, context: context);
}
},
child: const Text('Fetch More'),
),
ElevatedButton(
onPressed:
(stockSet.stocks.isEmpty) ? null : () => stockSet.clear(),
child: const Text('Clear'),
),
],
),
Expanded(
child: StockListViewBuilder(
stockSet: stockSet, theKey: myKey, alert: alertBar))
],
),
);
}
}
class StockListViewBuilder extends StatefulWidget {
final StockSet stockSet;
final GlobalKey theKey;
final Function alert;
const StockListViewBuilder({
Key? key,
required this.stockSet,
required this.theKey,
required this.alert,
}) : super(key: key);
#override
State<StockListViewBuilder> createState() => _StockListViewBuilderState();
}
class _StockListViewBuilderState extends State<StockListViewBuilder>
with asc_alertBar {
final ScrollController _scrollController = ScrollController();
late double _scrollPosition;
late double _maxScrollExtent;
late bool isThisTheEnd = false;
_scrollListener() async {
setState(() {
_scrollPosition = _scrollController.position.pixels;
_maxScrollExtent = _scrollController.position.maxScrollExtent;
});
if (!isThisTheEnd && _scrollPosition / _maxScrollExtent > 0.90) {
isThisTheEnd = true;
if (widget.stockSet.isRemoteEmpty) {
alertBar('No more items available', null, context: context);
} else {
await widget.stockSet.fetch(5);
}
}
if (isThisTheEnd && _scrollPosition / _maxScrollExtent <= 0.90) {
isThisTheEnd = false;
}
}
#override
void initState() {
super.initState();
int listCount;
_scrollController.addListener(_scrollListener);
WidgetsBinding.instance.addPostFrameCallback((_) async {
listCount = widget.stockSet.stocks.length;
if (listCount < 10 && !widget.stockSet.isRemoteEmpty) {
try {
await widget.stockSet.fetch(10);
} catch (e) {
super.setState(() {
widget.alert("Can't load stock.", widget.stockSet,
backgroundColor: Colors.red);
});
}
}
});
}
#override
Widget build(BuildContext context) {
return ListView.builder(
scrollDirection: Axis.vertical,
controller: _scrollController,
shrinkWrap: true,
key: widget.theKey,
itemCount: widget.stockSet.stocks.length + 1,
itemBuilder: (context, index) {
if (index <= widget.stockSet.stocks.length - 1) {
return InkWell(
onTap: (() => Navigator.pushNamed(
context,
'/stocks/stock',
arguments: ScreenArguments(widget.stockSet.stocks[index]),
)),
child: StockListItem(
stock: widget.stockSet.stocks[index],
));
} else {
return LoadingItemNotifier(
isLoading: widget.stockSet.isBusyLoading,
);
}
},
);
}
}
class StockSet extends ItemSet {
final List<Stock> _stocks = [];
List<Stock> get stocks => _stocks;
List<int> getHaveStocksIds() {
final List<int> ids = _stocks.map((stock) => stock.id).toList();
return ids;
}
void add(Stock stock) {
_stocks.add(stock);
notifyListeners();
}
void remove(Stock stock) {
_stocks.remove(stock);
notifyListeners();
}
void clear() {
_stocks.clear();
isBusyLoading = false;
isRemoteEmpty = false;
notifyListeners();
}
Future<void> fetch([int num = 1]) async {
int i = 0;
for (i; i < num; i++) {
if (!isRemoteEmpty) {
try {
Stock tmpStock = await _fetchAStock();
if (getHaveStocksIds().contains(tmpStock.id)) {
throw Exception('We allready have ${tmpStock.id}');
}
add(tmpStock);
} catch (e) {
i = num;
isRemoteEmpty = true;
isBusyLoading = false;
notifyListeners();
throw Exception('No more to fetch $e');
}
}
}
return;
}
Future<Stock> _fetchAStock() async {
List<int> have = getHaveStocksIds();
final queryParameters = {
'exclude': json.encode(have),
};
isBusyLoading = true;
notifyListeners();
try {
final response = await http.post(
Uri.https('onethread.design', 'agency/wp-json/ypagp/v1/pr-get-a',
queryParameters),
);
isBusyLoading = false;
notifyListeners();
if (response.statusCode != 200) {
throw HttpException('${response.statusCode}');
}
final Map<String, dynamic> map = json.decode(response.body);
return Stock(
id: map['id'] as int,
title: map['title'] as String,
description: map['description'] as String,
thumbUrl: map['thumbUrl'] as String,
);
} on SocketException {
feedback = 'Please enable an internet connection.';
notifyListeners();
} on HttpException {
feedback = "Couldn't find the/a post.";
notifyListeners();
} on FormatException {
feedback = "Bad response format.";
} catch (e, s) {
feedback = 'fetchA catch $e $s ';
}
throw Exception('Could not _fetchAStock');
}
}
class ItemSet extends ChangeNotifier {
bool isBusyLoading = false;
bool isRemoteEmpty = false;
String? feedback;
}
what I'm aiming at (and have partially succeeded in implementing) is something like
class StockSet extends ItemSet and class ItemSet extends ChangeNotifier {...
class Item extends Equatable {
const Item({
required this.id,
required this.title,
required this.description,
});
final int id;
final String title;
final String description;
#override
List<Object> get props => [id, title, description];
}
and I think then
class Stock extends Item {
final String thumbUrl;
const Stock(
{required super.id, required super.title, required super.description, required this.thumbUrl});
#override
List<Object> get props => [id, title, description, thumbUrl];
}
though when it comes to the ItemSet's methods, e.g.
been trying things like e.g.
in ItemSet
Future<Type> fetchAItem(String typeSlug, Type type) async {
List<int> have = getHaveItemsIds();
final queryParameters = {
'exclude': json.encode(have),
};
developer.log('_fetchAItem $queryParameters');
isBusyLoading = true;
notifyListeners();
try {
final response = await http.post(
Uri.https('###', 'a###/v1/$typeSlug-get-a',
queryParameters),
);
developer.log('response.statusCode:${response.statusCode}');
isBusyLoading = false;
notifyListeners();
if (response.statusCode != 200) {
throw HttpException('${response.statusCode}');
}
final Map<String, dynamic> map = json.decode(response.body);
return Item(
id: map['id'] as int,
title: map['title'] as String,
description: map['description'] as String,
) as type;// no! - was worth a guess though
} on...
but I'm getting lost in the plumbing, should I use #override in the child classes? seems clunky - or maybe use more mixins?.
Thanks in advance
and the alertBar mixin...
mixin asc_alertBar {
void alertBar(
String message,
ItemSet? itemSet, {
String label = 'Okay',
Function? action,
Color? backgroundColor,
required BuildContext context,
}) {
if (itemSet != null) {
String? itemSetFeedback = itemSet.feedback;
if (itemSetFeedback != null) {
message += '\n$itemSetFeedback';
itemSet.feedback = null;
}
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: backgroundColor,
duration: const Duration(seconds: 15),
content: AnimatedText(
textContent: message,
durationFactor: 0.25,
),
action: SnackBarAction(
label: label,
onPressed: () => action,
),
),
);
}
}
I hope the question makes sense.

Flutter Search in Listview from Firebase Realtime Database

This is the code to show how I get data from Firebase Realtime Database:
List musicList = <Song>[];
_MusicAppState() {
FirebaseDatabase.instance.reference().child("${getCurrentUID()}").once().then((dataSnapshot){
print("Sucessfully loaded the data");
List tmpList = [];
dataSnapshot.value.forEach((k,v){
tmpList.add(v);
});
musicList = tmpList;
setState(() {
});
}).catchError((error) {
print("Failed to loaded the data");
});
}
This is the part of I show the musicList in Listview:
Expanded(
child: ListView.builder(
// List musicList = <Song>[];
itemCount: musicList.length,
itemBuilder: (context, index) => customListTitle(
onTap: () {
playMusic(musicList[index]['url']);
setState(() {
_currentTitle = musicList[index]['title'];
_currentSinger = musicList[index]['singer'];
_currentCover = musicList[index]['cover'];
});
},
title: musicList[index]['title'],
singer: musicList[index]['singer'],
cover: musicList[index]['cover'],
),
),
I'm trying to build a few search function but all of them it didn't work.
Here is one of the search functions that I did:
Widget buildSearch() => SearchWidget(
text: query,
hintText: 'Songs name',
onChanged: searchSong,
);
void searchSong(String query) {
final songs = musicList.where((song) {
final titleLower = song.title.toLowerCase();
final searchLower = query.toLowerCase();
return titleLower.contains(searchLower);
}).toList();
setState(() {
this.query = query;
this.musicList = songs;
});
}
}
This is also my song.dart file:
class Song {
final String title;
final String singer;
final String cover;
final String url;
const Song({
required this.title,
required this.singer,
required this.cover,
required this.url,
});
}
Can someone let me know what's wrong of my code? My search function didn't work

How to load data on app start from file with Flutter and sqflite?

I have a whole list of data I want to load into my app when it starts up. I was able to extract into a JSON file so I can load it into my table, but it's not quite loading correctly and also seems to get added multiple times. It looks like I am loading in the file correctly via assets (printing the values in _loadIngredients gives me the right results). But when it is saved into the DB, it just gets saved as an integer and that is what shows up in the view.
I could not find a good example of this anywhere.
Where am I going wrong?
In my DatabaseHandler:
class DatabaseHandler{
DatabaseHandler._();
static final DatabaseHandler db = DatabaseHandler._();
static DatabaseHandler get() {
return db;
}
static Database _database;
final databaseName = "recipe.db";
DatabaseHandler._createInstance();
Future<Database> get database async {
if (_database != null)
return _database;
_database = await initDB();
return _database;
}
initDB() async {
var path = await getDatabasesPath();
print("Creating tables at path: $path");
var dbPath = join(path, 'recipe.db');
Database dbConnection = await openDatabase(dbPath, version: 1,
onCreate: (Database db, int version) async {
return db.execute(
"CREATE TABLE ingredients(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)",
);
}
);
await _loadIngredients(dbConnection);
return dbConnection;
}
_loadIngredients(Database db) async {
Batch batch = db.batch();
List<Map<String, dynamic>> records = await db.query('ingredients');
print(records);
String ingredientsJson = await rootBundle.loadString('assets/json/ingredients.json');
List ingredientsList = json.decode(ingredientsJson);
ingredientsList.forEach((val) {
print(val);
Ingredient ingredient = Ingredient.fromMap(val);
batch.insert("ingredients", ingredient.toMap(false));
});
var results = await batch.commit();
}
}
My ingredient class:
class Ingredient {
int id;
String name;
String categoryName;
DateTime dateCreated;
Ingredient(this.id, this.name, this.categoryName, this.dateCreated);
Map<String, dynamic> toMap(bool forUpdate) {
if(dateCreated == null) {
dateCreated = new DateTime.now();
}
var data = {
// 'id': id, since id is auto incremented in the database we don't need to send it to the insert query.
'name': utf8.encode(name),
'category_name': utf8.encode(categoryName),
'date_created': epochFromDate( dateCreated )
};
if(forUpdate){
data["id"] = this.id;
}
return data;
}
Ingredient.fromMap(Map map){
id = map["id"];
name = map["name"];
categoryName = map["category_name"];
dateCreated = map["date_created"];
}
// Converting the date time object into int representing seconds passed after midnight 1st Jan, 1970 UTC
int epochFromDate(DateTime dt) {
return dt.millisecondsSinceEpoch ~/ 1000 ;
}
// overriding toString() of the note class to print a better debug description of this custom class
#override toString() {
return {
'id': id,
'name': name,
'category_name': categoryName,
'date_created': epochFromDate( dateCreated )
}.toString();
}
}
My homepage class where I am initializing my DB:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Map<String, dynamic>> _allIngredientsInQueryResult = [];
var notesViewType ;
#override void initState() {
super.initState();
notesViewType = viewType.Staggered;
DatabaseHandler.db.initDB();
retrieveAllIngredientsFromDatabase();
}
#override
Widget build(BuildContext context) {
return
Container(
child: _ingredientList()
);
}
Widget _ingredientList() {
return Container(
child: ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: _allIngredientsInQueryResult.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
color: Colors.amber[100],
child: Center(child: Text('Entry ${_allIngredientsInQueryResult[index]["name"]}')),
);
},
separatorBuilder: (BuildContext context, int index) => const Divider(),
)
);
}
retrieveAllIngredientsFromDatabase() async {
var _testData = await DatabaseHandler.db.selectAllIngredients();
setState(() {
_allIngredientsInQueryResult = _testData;
});
}
}
Image of what I am seeing in the app:
Ingredients json
if you use utf8.encode(name), you convert your String in bytes something like flour = [102, 108, 111, 117, 114]
and when you display this values you must also set a utf8.decode(map["name"])
in your example something like
Text('Entry ' + utf8.decode(${_allIngredientsInQueryResult[index]["name"]})))
every time your initDB() is calling, the Data comes again in the DB. You can do it only in the onCreate part of the Sqlite DB
initDB() async {
var path = await getDatabasesPath();
print("Creating tables at path: $path");
var dbPath = join(path, 'recipe.db');
Database dbConnection = await openDatabase(dbPath, version: 1,
onCreate: (Database db, int version) async {
await db.execute(
"CREATE TABLE ingredients(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, category_name TEXT, date_created INTEGER)",
);
await _loadIngredients(db);
}
);
return dbConnection;
}
i would also use your model class and not a dynimac map
Define _allIngredientsInQueryResult
List<Ingredient> _allIngredientsInQueryResult = new List();
get selectAllIngredients, with the fromMap()
Future<List<Ingredient>> selectAllIngredients() async {
var dbClient = await database;
List<Map> result = await dbClient.query('ingredients');
List<Ingredient> r_ingredient = result.map((i) => Ingredient.fromMap(i)).toList();
return r_ingredient;
}
set Decode in the fromMap()
Ingredient.fromMap(Map map){
id = map["id"];
name = utf8.decode(map["name"]);
categoryName = utf8.decode(map["category_name"]);
dateCreated = DateTime.fromMillisecondsSinceEpoch(map["date_created"]);
}
get Ingredients
retrieveAllIngredientsFromDatabase() async {
DatabaseHandler.db.selectAllIngredients().then((List<Ingredient> r_ingredient) {
setState(() {
_allIngredientsInQueryResult = r_ingredient;
});
});
}
display in the Listview
Text('Entry ' + _allIngredientsInQueryResult[index].name)