Overview:
App architecture details:
state management -> provider (not sure this is relevant, but in case you are interested)
data storage -> SQFlite
Specific issue details:
I have a calendarDay data model, with a property of
DateTime date;
I know that DateTime is not supported in SQLite (not in SQFlite either) and the recommendation is to use a String or Integer . I am struggling with how to actually do that.
Error I am getting:
flutter: *** WARNING ***
Invalid argument 2021-07-01 15:09:11.129598 with type DateTime.
Only num, String and Uint8List are supported. See https://github.com/tekartik/sqflite/blob/master/sqflite/doc/supported_types.md for details
This will throw an exception in the future. For now it is displayed once per type.
This is my setup:
calendar_day.dart
class CalendarDay {
int? id;
DateTime date;
CalendarDay(
{this.id,
required this.date});
// encode to SQLite database
Map<String, dynamic> toMap() {
final map = Map<String, dynamic>();
map['id'] = id;
map['date'] = date.toIso8601String(); //toString(); this toString did not work //jsonEncode(date) -> SERIALIZE THE ARRAYS INTO JSON strings, this did not work
return map;
}
// decode from SQLite database
static fromMap(Map map) {
return CalendarDay(
id: map['id'],
date: DateTime.parse(map['date']), // jsonDecode(map['date']));
}
}
database_client.dart
class DatabaseClient {
Future<Database> initializedDatabase() async {
WidgetsFlutterBinding.ensureInitialized();
String path = await getDatabasesPath();
return openDatabase(
join(path, 'three_things_database.db'),
onCreate: (database, version) async {
await database.execute(
"CREATE TABLE ${Strings.calendarDayDataBase} (id INTEGER PRIMARY KEY, date TEXT)",
); },
version: 1, ); }
// Create / insert calendarDay
Future<void> insertCalendarDay(CalendarDay day) async {
final Database database = await initializedDatabase();
await database.insert(
Strings.calendarDayDataBase,
day.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
}
I am thinking the problem lies with the toMap() method, since the error notes the DateTime object. But I am a bit stuck and don't really know how to get around this. Any help is greatly appreciated.
Additional things I have tried in toMap() :
I did include the code commented out, but for clarity, I'll post here:
I tried mapping the DateTime object to a JSONString. This would hopefully be enough for storing the map in SQLite database, but this approach threw the same error.
Tried mapping to a regular String using date.toString(). This did not work either. Since the recommendation (link above) from the SQFlite folks is to use ISO8601 string, I thought this approach would work.
Related question(s), yet did not solve my question:
Create DateTime column in SQFlite
Here's a clear example :
var dt = DateTime.now();
// String
var dtStr = dt.toIso8601String();
dt = DateTime.tryParse(dtStr);
// Int
var dtInt = dt.millisecondsSinceEpoch;
dt = DateTime.fromMillisecondsSinceEpoch(dtInt);
Related
https://api.covid19api.com/summary
This is the API I am using now I can fetch the global data by the below code I want to fetch data of a single Country(India) by the same method. If there is no method by which I can get the data then if I use "https://api.covid19api.com/total/dayone/country/India" then how to get the daily confirmed cases.?
class GlobalSummaryModel{
final int newConfirmed;
final int totalConfirmed;
final int newDeaths;
final int totalDeaths;
final int newRecovered;
final int totalRecovered;
final DateTime date;
GlobalSummaryModel(this.newConfirmed, this.totalConfirmed, this.newDeaths, this.totalDeaths, this.newRecovered, this.totalRecovered, this.date);
factory GlobalSummaryModel.fromJson(Map<String, dynamic> json){
return GlobalSummaryModel(
json["Global"]["NewConfirmed"],
json["Global"]["TotalConfirmed"],
json["Global"]["NewDeaths"],
json["Global"]["TotalDeaths"],
json["Global"]["NewRecovered"],
json["Global"]["TotalRecovered"],
DateTime.parse(json["Date"]),
);
}
}
Please provide me the code if you can that will be more helpful for me I am new in fetching data from the rest API.
The API also returns a Countries field in the response, which contains data for India. You can extract that data like so:
final countries = json["Countries"];
final Map<String, dynamic> indiaSummaryData = countries.firstWhere((map) {
return map["CountryCode"] == "IN";
});
I am trying to save the list data into the GetX Storage i.e. coming from firestore in my application.
But somehow, It's not executing as expected. Every time I got the error saying: Unhandled Exception: Converting object to an encodable object failed: Instance of 'DocumentData'
Below, List variable that I have created:
RxList<DocumentData> todoList = <DocumentData>[].obs;
Below, My function where I am trying to write the values in GetX Storage.
//Function to get the all todos from firestore
getAllTodosFromFirestore() async {
await todos.where('uid', isEqualTo: uid).get().then((value) {
Get.find<TodoController>().todoList.value = value.docs.map((e) => DocumentData.fromJson(e.data() as Map<String,dynamic>)).toList();
GetStoragePref.instance.writeValues('todos_list',Get.find<TodoController>().todoList);
});
return Get.find<TodoController>().todoList;
}
Below, My DocumentData model class.
class DocumentData {
DocumentData({
required this.uid,
required this.timer,
required this.itemName,
required this.isSelectedItem,
});
String uid;
Timestamp timer;
String itemName;
bool isSelectedItem;
factory DocumentData.fromJson(Map<String, dynamic> json) => DocumentData(
uid: json["uid"],
timer: json["timer"],
itemName: json["itemName"],
isSelectedItem: json["isSelectedItem"],
);
Map<String, dynamic> toJson() => {
"uid": uid,
"timer": timer,
"itemName": itemName,
"isSelectedItem": isSelectedItem,
};
}
Can anyone please help me to resolve this?
You can't store custom types (DocumentData in your case) directly on GetStorage. You can save the raw JSON (in your case it should be somewhere inside the value.docs) & when getting the data from GetStorage, parse the raw JSON to DocumentData again.
I'm creating todo app in flutter. And I need to show Todos date wise. Like all Todos created today should shown under Today, all the tomorrow's Todos should shown under Tomorrow.
I have created my table like this:
database.execute("""
CREATE TABLE Todotable(
id INTEGER PRIMARY KEY AUTOINCREMENT,
taskName TEXT NOT NULL,
taskTag TEXT NOT NULL,
date TEXT NOT NULL,
isReminder INTEGER NOT NULL,
isCompleted INTEGER NOT NULL
)
""");
I don't know how to query SQFlite data date wise and format it like Today and Tomorrow. And show in section like today and tomorrow as shown in design.
Thanks for answers:)
for getting todos for tomorrow
//for tomorrow
String tomorrowDate= DateTime.now().add(Duration(days: 1)).toIso8601String();
var todosForTomrrow= await database
.rawQuery('SELECT * FROM Todotable WHERE date = ?', [tomorrowDate]);
//for today
String todayDate= DateTime.now().toIso8601String();
var todosForToday= await database
.rawQuery('SELECT * FROM Todotable WHERE date = ?', [todayDate]);
Date is converted and saved here in string format and date should converted to same format before inserting into the table like this
You can create DATETIME columns in sqflite.
Here is an example for a Weather table created in a sqflite database:
batch.execute('''
CREATE TABLE $tableWeather (
$weatherLocalization TEXT NOT NULL,
$weatherDate DATETIME NOT NULL,
$weatherHumidityPercentage REAL,
$weatherWindDegree REAL,
$weatherWindSpeed REAL,
$weatherPressureHPascal INTEGER,
$weatherTemperatureCelsius REAL,
$weatherDescription TEXT,
$weatherIconId TEXT,
PRIMARY KEY($weatherLocalization, $weatherDate));
''',);
You can create a Weather object as follows:
class Weather{
String localization;
DateTime date;
double humidityPercentage;
double windDegree;
double windSpeedMS;
int pressureHPascal;
double temperatureCelsius;
String description;
String iconId;
Weather({
#required this.localization,
#required this.date,
this.humidityPercentage,
this.windDegree,
this.windSpeedMS,
this.pressureHPascal,
this.temperatureCelsius,
this.description,
this.iconId
});
//to be used when inserting a row in the table
Map<String, dynamic> toMap() {
final map = new Map<String, dynamic>();
map["$weatherLocalization"] = localization;
map["$weatherDate"] = date.toString();
map["$weatherHumidityPercentage"] = humidityPercentage;
map["$weatherWindDegree"] = windDegree;
map["$weatherWindSpeed"] = windSpeedMS;
map["$weatherPressureHPascal"] = pressureHPascal;
map["$weatherTemperatureCelsius"] = temperatureCelsius;
map["$weatherDescription"] = description;
map["$weatherIconId"] = iconId;
return map;
}
//to be used when converting the row into object
factory WeatherOnDate.fromMap(Map<String, dynamic> data) => new WeatherOnDate(
localization: data["$weatherLocalization"],
date: DateTime.parse(data["$weatherDate"]),
humidityPercentage: data["$weatherHumidityPercentage"],
windDegree: data["$weatherWindDegree"],
windSpeedMS: data["$weatherWindSpeed"],
pressureHPascal: data["$weatherPressureHPascal"],
temperatureCelsius: data["$weatherTemperatureCelsius"],
description: data["$weatherDescription"],
iconId: data["$weatherIconId"]
);
}
Be careful to transform your DateTime attribute to a String or int as I did in the toMap() function.
Then, when you want to fetch a date you can do this:
Future<Weather> fetchWeatherOnDate(DateTime dateTime) async {
DatabaseHelper _databaseHelper = Injection.injector.get();
List<Map<String, dynamic>> weatherMaps = await _databaseHelper.db.rawQuery(
'SELECT * FROM $tableWeather WHERE DATE($weatherDate) = DATE(?)',
[dateTime.toString()]);
List<Weather> weathers= [];
for (final weatherMap in weatherMaps) {
weathers.add(Weather.fromMap(weatherMap));
}
if (weathers.isNotEmpty){
return weathers[0];
}
return null;
}
DateTime today = DateTime.now()
Weather weatherToday = fetchWeatherOnDate(today);
I think that it gives you a good idea of how to solve your problem :)
DateTime is not supported in Flutter SQLite package.
Check the information about supported SQLite types:
https://pub.dev/packages/sqflite#supported-sqlite-types
DateTime is not a supported SQLite type. Personally I store them as int (millisSinceEpoch) or string (iso8601)
We can use String or int as suggested from the package developer.
To work with a conditional selection of items of day in DB, perhaps a String is more convenient.
Personally, I prefer user formatter to get the search keyword, as shown below:
DateFormat formater = DateFormat('yyyyMMdd');
var today = formater.format(DateTime.now());
var tomorrow = formater.format(DateTime.now().add(const Duration(days: 1)));
To search in DB, I'd use a method like below:
Future<TodoList?> getTodoListByDay(String day) async {
final db = await database;
String sql =
"SELECT * FROM Todotable WHERE date = \'${day}\' ";
var res = await db.rawQuery(sql);
List<TodoList> objs = res.isNotEmpty
? res.map((c) => TodoList.fromMap(c)).toList()
: [];
return objs.isNotEmpty ? objs : null;
}
TodoList class could be generate from json, as I do with 'jsonToDartModel' package.
import 'dart:convert';
class TodoList {
int? id;
String? taskName;
String? taskTag;
String? date;
int? isReminder;
int? isCompleted;
TodoList({
this.id,
this.taskName,
this.taskTag,
this.date,
this.isReminder,
this.isCompleted,
});
factory TodoList.fromMap(Map<String, dynamic> data) => TodoList(
id: data['id'] as int?,
taskName: data['taskName'] as String?,
taskTag: data['taskTag'] as String?,
date: data['date'] as String?,
isReminder: data['isReminder'] as int?,
isCompleted: data['isCompleted'] as int?,
);
Map<String, dynamic> toMap() => {
'id': id,
'taskName': taskName,
'taskTag': taskTag,
'date': date,
'isReminder': isReminder,
'isCompleted': isCompleted,
};
/// `dart:convert`
///
/// Parses the string and returns the resulting Json object as [TodoList].
factory TodoList.fromJson(String data) {
return TodoList.fromMap(json.decode(data) as Map<String, dynamic>);
}
/// `dart:convert`
///
/// Converts [TodoList] to a JSON string.
String toJson() => json.encode(toMap());
}
i just wanted to ask since this thing got me confused, i am still beginner with OOP i started it with Java and now working with it in flutter, so basically when i use a model in flutter, am i using it to fetch data from an api or a web server, am i right? let's say it's like select .. from .. in SQL, is that right? for example here i have this model of location
import './location_fact.dart';
class Location {
final String name;
final String url;
final List<LocationFact> facts;
Location({this.name, this.url, this.facts});
}
so basically in final name and final url i am specifying which data to get from the api or the web server ( in the example i am just giving fake data which just data i am giving it manually without a third party api or web server ) so when i use these i am just like using select name, url from "apĂ®" ? is that the deal here? and when i am using the Location({this.name, this.url, this.facts}) am i specifying which data this model will take as a parameter ? and when i am using final am i like referring to the data that it won't be fetched again once it's fetched? and when i am using final list <LocationFact> facts; am i specifying that this data is going to take the facts only from the list or what? i know this is overwhelming but i am really beginner with dart and flutter generally, i appreciate anyone's help and thank you.
I think you're reading too much magic into the word "model". In the original "MVC" triad, there are Models (places to stash data), Views (basically Widgets in Flutter), and Controllers (generally buried in Widgets in Flutter, but can be and should be pulled out to testable and reusable logic). Does that help?
First of all if you fetching data from API it will return data in json format as json Object or json List after fetching data from API you can use json data or you can convert json Object to Plain Dart Object
To convert json data to Plain Dart Object you have to specify your model class.
Here is an example to design a model class
class Location {
String name;
String url;
List<Facts> facts;
Location({this.name, this.url, this.facts});
Location.fromJson(Map<String, dynamic> json) {
name = json['name'];
url = json['url'];
if (json['facts'] != null) {
facts = new List<Facts>();
json['facts'].forEach((v) {
facts.add(new Facts.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name'] = this.name;
data['url'] = this.url;
if (this.facts != null) {
data['facts'] = this.facts.map((v) => v.toJson()).toList();
}
return data;
}
}
class Facts {
String locationFact;
Facts({this.locationFact});
Facts.fromJson(Map<String, dynamic> json) {
locationFact = json['locationFact'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['locationFact'] = this.locationFact;
return data;
}
}
Here Location.fromJson() is an factory method to convert your json object to Plain Dart Object
For reference you should take a tour into the Official Documentation
Fetch Data From Network Flutter Documentation
This is a Flutter project using Floor (package equivalent of Jetpack's Room).
I have an entity with an auto increment id (note that this is pre null-safety code, in the answer I start now with post null safety snippet):
const String ACTIVITIES_TABLE_NAME = 'activities';
#Entity(
tableName: ACTIVITIES_TABLE_NAME,
indices: [
Index(value: ['start'])
],
)
class Activity {
#PrimaryKey(autoGenerate: true)
int id;
#ColumnInfo(name: 'device_name')
final String deviceName;
#ColumnInfo(name: 'device_id')
final String deviceId;
final int start;
Activity({
this.deviceName,
this.deviceId,
this.start,
});
}
I have this DAO:
#dao
abstract class ActivityDao {
#Query('SELECT * FROM $ACTIVITIES_TABLE_NAME ORDER BY start DESC')
Future<List<Activity>> findAllActivities();
#insert
Future<int> insertActivity(Activity activity);
}
In my app I recorded five activities so far, hunky-dory.
_database =
await $FloorAppDatabase.databaseBuilder('app_database.db').build();
...
_activity =
Activity(deviceName: device.name, deviceId: device.id.id, start: _lastRecord.millisecondsSinceEpoch);
final id = await _database.activityDao.insertActivity(_activity);
_activity.id = id;
Then in another view I list them, but there is a lurking problem.
final data = await _database.activityDao.findAllActivities();
When I query the entities with this dead simple method, all the five or so activities in my DB are returned with an id field filled with null. That makes the entity completely useless because any other operation I would like to perform on it fails due to lack of actual id. Am I doing something wrong?
I mostly have experience with MySQL, Postgres and non SQLite RDBMS. As I understand in SQLite every row has a unique rowid, and by declaring my auto increment id primary key field basically I alias that rowid? Whatever it is, I need the id. It cannot be null.
I'm debugging the guts of Floor.
Future<List<T>> queryList<T>(
final String sql, {
final List<dynamic> arguments,
#required final T Function(Map<String, dynamic>) mapper,
}) async {
final rows = await _database.rawQuery(sql, arguments);
return rows.map((row) => mapper(row)).toList();
}
At this point the rows still have the ids filled in properly, although the entities in the rows are just a list of dynamic values. The rows.map supposed to map them to the entity objects, and that cannot carry the id over for some reason? Can this be a Floor bug?
Ok, now I see that in the generated database.g.dart the mapper does not have the id:
static final _activitiesMapper = (Map<String, dynamic> row) => Activity(
deviceName: row['device_name'] as String,
deviceId: row['device_id'] as String,
start: row['start'] as int);
That explains it why id is null then. But this is generated code, how can I tell Floor I need the id? Or why should I tell it, it has to be there by default, who wouldn't want to know the primary key of an object?
Ok, so this is because I didn't have the the id as a constructor parameter. I didn't have that because it's an auto increment field and I'm not the one who determines it. However without having it as a constructor argument, the generated code cannot pass it along with the mapper as a parameter, so it leaves it out from the mapper. So I added the id as a constructor argument.
Post null-safety version:
class Activity {
#PrimaryKey(autoGenerate: true)
int? id;
#ColumnInfo(name: 'device_name')
final String deviceName;
#ColumnInfo(name: 'device_id')
final String deviceId;
final int start;
final int end;
Activity({
this.id,
required this.deviceName,
required this.deviceId,
required this.start,
this.end: 0,
});
}
I also add this Floor GitHub issue here, it looks like in the future there might be an annotation: https://github.com/vitusortner/floor/issues/527
Pre null-safety version:
import 'package:meta/meta.dart';
class Activity {
#PrimaryKey(autoGenerate: true)
int id;
#ColumnInfo(name: 'device_name')
#required
final String deviceName;
#ColumnInfo(name: 'device_id')
#required
final String deviceId;
final int start;
Activity({
this.id: null,
this.deviceName,
this.deviceId,
this.start,
});
}
The compiler is a little iffy about the null, but after the flutter packages pub run build_runner build the database.g.dart's mappers have the id as well.