I am working on a Flutter app and I am testing on both Android and iOS devices. My app works perfectly on a physical Android device and it works on an iOS device with iOS 13. The problem is that since I updated the iOS device to iOS 14.3, I cannot insert into the database with SQFlite.
What does work
Android devices
iOS devices with iOS older than 14
Reading from the database (meaning items that were inserted into the database when the phone was on iOS 13 can successfully be retrieved after updating to iOS 14.3)
What does not work
Inserting into the database on iOS 14.3.
await db.transaction((txn) async {
return await txn.insert(TodoList.TABLENAME, TodoList.toMap(todoList),
conflictAlgorithm: ConflictAlgorithm.replace);
});
The app does not provide any error logs and it also does not crash; I can continue to interact with the app. It behaves as if it just disregards the database insert. I've tried a few different things to get it to work but I am at a loss. I appreciate any advice!
This is the first thing I tried: Flutter sqflite app does not working on real ios device
I also tried a workaround mentioned here (although it may not be relevant to my issue): Modify schema structure fails on iOS 14
Relevant Code
Dependencies (Edit: Issue occurs on any SQFlite version, even 1.3.2+2):
sqflite: ^1.3.0
path_provider: ^0.4.1
Database Class:
import 'dart:io';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:todo_list/data/todo_list.dart';
class DatabaseHelper {
//Create a private constructor
DatabaseHelper._();
static const databaseName = 'todos_database.db';
static final DatabaseHelper instance = DatabaseHelper._();
static Database _database;
Future<Database> get database async {
if (_database == null) {
return await initializeDatabase();
}
return _database;
}
initializeDatabase() async {
return await openDatabase(join(await getDatabasesPath(), databaseName),
version: 1, onCreate: (Database db, int version) async {
// Possible fix for iOS issue (did not work)
if (Platform.isIOS) {
await db.execute('PRAGMA sqflite -- db_config_defensive_off');
}
await db.execute(
"CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, listName TEXT, items TEXT, completed TEXT, count INTEGER, color INTEGER, ordering INTEGER)");
});
}
Future<List<TodoList>> retrieveTodos() async {
final Database db = await database;
final List<Map<String, dynamic>> maps = await db.query(TodoList.TABLENAME);
return List.generate(maps.length, (i) {
return TodoList(
id: maps[i]["id"],
listName: maps[i]["listName"],
items: maps[i]["items"],
completed: maps[i]["completed"],
count: maps[i]["count"],
color: maps[i]["color"],
ordering: maps[i]["ordering"],
);
});
}
insertTodo(TodoList todoList) async {
final db = await database;
await db.transaction((txn) async {
return await txn.insert(TodoList.TABLENAME, TodoList.toMap(todoList),
conflictAlgorithm: ConflictAlgorithm.replace);
});
//return res;
}
updateTodo(TodoList todoList) async {
final db = await database;
await db.update(TodoList.TABLENAME, TodoList.toMap(todoList),
where: "id = ?",
whereArgs: [todoList.id],
conflictAlgorithm: ConflictAlgorithm.replace);
}
deleteTodo(int id) async {
var db = await database;
db.delete(TodoList.TABLENAME, where: "id = ?", whereArgs: [id]);
}
}
Call to insertToDo:
await DatabaseHelper.instance.insertTodo(TodoList(
listName: listName,
items: encodedTodo,
completed: encodedDone,
count: 0,
color: _mainColor.value,
ordering: order));
UPDATE
Attempted raw SQL query. No change, works fine on Android but not on iOS 14.3.
Insert function with raw query:
insertTodo(TodoList todoList) async {
final db = await database;
await db.transaction((txn) async {
return await txn.rawInsert(
'INSERT INTO todos(listName, items, completed, count, color, ordering) VALUES(?, ?, ?, ?, ?, ?)',
[
todoList.listName,
todoList.items,
todoList.completed,
todoList.count,
todoList.color,
todoList.ordering
]);
/*await txn.insert(TodoList.TABLENAME, TodoList.toMap(todoList),
conflictAlgorithm: ConflictAlgorithm.replace);*/
});
//return res;
}
Note, I've also tried insert without transaction.
insertTodo(TodoList todoList) async {
final db = await database;
return await db.insert(TodoList.TABLENAME, TodoList.toMap(todoList),
conflictAlgorithm: ConflictAlgorithm.replace);
}
Related
I'm having an issue getting a Drive ID when making a GET request.
First I'm creating the Team using "https://graph.microsoft.com/v1.0/teams". The request returns no response but a Status 202 accepted, the new Team is listed on Teams. I then extract the Team ID from Response Headers "Location".
Here is the failure, I make a request to https://graph.microsoft.com/v1.0/groups/{{TeamsID}}/drive
Here is my response back
{"error":{"code":"ResourceNotFound","message":"Resource is not found.","innerError":{"date":"2021-05-03T19:17:03","request-id":"75b4888d-de91-41aa-89ef-d983fcb5238d","client-request-id":"75b4888d-de91-41aa-89ef-d983fcb5238d"}}}
I make the same request on via postman and it works fine.
Here is my code
try {
let crt = await createTeam(msalData, data);
crt.headers.forEach( async (val, key) => {
if (key === "location") {
let id = val.split("/teams('")[1].split("')/operations")[0];
let gdd = await getDocumentDrive(msalData, id);
console.log(gdd)
}
});
} catch (err) {
console.log(err);
}
I can't figure out what I'm doing wrong.
Apparently the error (ResourceNotFound) occurs since the moment when Get Drive request is submitted the Group is not yet completely provisioned, documentations says the following in this regard:
If the group was created less than 15 minutes ago, it's possible for
the Create team call to fail with a 404 error code due to replication
delays. The recommended pattern is to retry the Create team call three
times, with a 10 second delay between calls.
Since you haven't revealed how exactly createTeam function is implemented, retry pattern come to the rescue here, for example:
async function withRetry(fn, maxRetry = 5, ms = 1000) {
try {
const val = await fn();
return val;
} catch (error) {
if (maxRetry) {
await new Promise((r) => setTimeout(r, ms));
return withRetry(fn, maxRetry - 1, ms);
} else throw new Error(`Max retries reached for function ${fn.name}`);
}
}
and now Teams could be created like this (note, in retry logic Get Group endpoint request is submitted to ensure it has been provisioned):
async function ensureTeam(client, teamName, defaultOwnerId) {
const teamResp = await createTeam(client, teamName, defaultOwnerId);
const teamloc = teamResp.headers.get("Content-Location");
return await withRetry(
async () => {
const groupUrl = teamloc.replace("teams", "groups");
return await client.api(groupUrl).get();
},
5,
10000
);
}
where createTeam could look like this:
async function createTeam(client, teamName, defaultOwnerId) {
const team = {
"template#odata.bind":
"https://graph.microsoft.com/v1.0/teamsTemplates('standard')",
displayName: teamName,
description: teamName,
members: [
{
"#odata.type": "#microsoft.graph.aadUserConversationMember",
roles: ["owner"],
"user#odata.bind": `https://graph.microsoft.com/v1.0/users('${defaultOwnerId}')`,
},
],
};
return await client.api("/teams").responseType(ResponseType.RAW).post(team);
}
Usage
const graphClient = Client.initWithMiddleware({ authProvider });
const group = await ensureTeam(graphClient,"Demo team",ownerId);
const driveUrl = `/groups/${group.id}/drive`;
const drive = await graphClient.api(driveUrl).get();
I'm new to flutter/iOS.
I'm using:
Flutter 1.22.6 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 9b2d32b605 • 2021-01-22 14:36:39 -0800
Engine • revision 2f0af37152
Tools • Dart 2.10.5
flutter_downloader: ^1.4.4
I have to correct an application that I did not code I'm trying to understand it. It downloads a pdf file and open it, but is not working in iOS.
All the configuration that I read in https://github.com/fluttercommunity/flutter_downloader is correct.
Flutter doctor is OK.
Below I show you parts of the code
main.dart
final _prefs = SharedPreferences();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final prefs = SharedPreferences();
await prefs.initPrefs();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown
]);
WidgetsFlutterBinding.ensureInitialized();
await FlutterDownloader.initialize(debug: true);
_prefs.uid = await getId();
runApp(MyApp());
}
pages/registry/facture.dart
List<Widget> _actionsCreateBar(BuildContext context) {
return <Widget>[
(document.id != null)
? IconButton(
icon: Icon(EvaIcons.downloadOutline),
onPressed: () async {
_downloadAction(); // This method is executed when user press download icon
},
color: primaryColor,
iconSize: 25,
)
: Container(),
];
}
void _downloadAction() async {
if (await utils.isInternetAvailable()) {
if (await _validateUrlRideBeforeDownload()) {
await _pdfBloc.downloadPdf(document.url_ride, Theme.of(context).platform);
setState(() {});
return;
}
_showDialogOk(
context, 'Download', 'Wait please');
} else {
_showDialogOk(context, 'Info',
'No conection');
}
}
bloc/pdf/pdfbloc.dart
class PdfBloc {
final _downloadingController = BehaviorSubject<bool>();
final _loadingController = BehaviorSubject<bool>();
final _progressStringController = BehaviorSubject<String>();
final _pdfProvider = DownloadProvider();
Stream<String> get progressStringStream => _progressStringController.stream;
Stream<bool> get loadingStream => _loadingController.stream;
Stream<bool> get downloadingStream => _downloadingController.stream;
Future<ResponseData> downloadPdf(String url, var platform) async {
_downloadingController.sink.add(true);
ResponseData resData = await _pdfProvider.downloadPdf(url, _progressStringController, platform);
_downloadingController.sink.add(false);
return resData;
}
dispose() {
_downloadingController.close();
_progressStringController.close();
_loadingController.close();
}
}
provider/download/downloadprovider.dart
class DownloadProvider {
Future<ResponseData> downloadPdf(String url, dynamic progressString, var platform) async {
ResponseData resData = ResponseData();
final _prefs = SharedPreferences();
try {
var path = await findLocalPath(platform) + '/';
FlutterDownloader.cancelAll();
final String taskId = await FlutterDownloader.enqueue(
url: url,
savedDir: path,
showNotification: true, // show download progress in status bar (for Android)
openFileFromNotification: true, // click on notification to open downloaded file (for Android)
headers: {HttpHeaders.authorizationHeader: _prefs.token, 'Content-type': 'application/json'},
);
// Last developer used this "while" to wait while a dialog is shown
// Android behaviour: flutter says "only success task can be opened" but then it works
// iOS behaviour: flutter says "only success task can be opened" infinitely and never
// shows the pdf
// In iOS this loop iterates forever
while(!await FlutterDownloader.open(taskId: taskId,)) {
// Last developer did this validation, but I don't know why
if (platform == TargetPlatform.iOS) {
await FlutterDownloader.open(taskId: taskId);
}
}
_setResponseData(resData, 'Completed', false);
return resData;
} catch(e) {
_setResponseData(resData, 'Error', true);
return resData;
}
}
_setResponseData(ResponseData resData, String message, bool state) {
resData.setData(message);
resData.setError(state);
}
}
Future<String> findLocalPath(var platform) async {
final directory = platform == TargetPlatform.android
? await getExternalStorageDirectory()
: await getApplicationDocumentsDirectory();
return directory.path;
}
I have tried several versions of ios and iphone without success.
Any ideas?
Please help me, I'm stuck.
Thanks.
I could to solve this problem. The previous developers committed a bad programming practice, which caused a race condition in ios when trying to force open a task without checking its status.
I had to change the "while" loop and within it, check the status and progress of the download task. Once it reached 100% progress and its status was complete, then we break the loop and finally open the task.
In provider/download/downloadprovider.dart
bool waitTask = true;
while(waitTask) {
String query = "SELECT * FROM task WHERE task_id='" + taskId + "'";
var _tasks = await FlutterDownloader.loadTasksWithRawQuery(query: query);
String taskStatus = _tasks[0].status.toString();
int taskProgress = _tasks[0].progress;
if(taskStatus == "DownloadTaskStatus(3)" && taskProgress == 100){
waitTask = false;
}
}
await FlutterDownloader.open(taskId: taskId);
open your ios project in Xcode
Add sqlite library.
Configure AppDelegate:
/// AppDelegate.h
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#interface AppDelegate : FlutterAppDelegate
#end
// AppDelegate.m
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
#include "FlutterDownloaderPlugin.h"
#implementation AppDelegate
void registerPlugins(NSObject<FlutterPluginRegistry>* registry) {
if (![registry hasPlugin:#"FlutterDownloaderPlugin"]) {
[FlutterDownloaderPlugin registerWithRegistrar:[registry registrarForPlugin:#"FlutterDownloaderPlugin"]];
}
}
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
[FlutterDownloaderPlugin setPluginRegistrantCallback:registerPlugins];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
#end
Completely disable ATS: (add following codes to your Info.plist file)
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key><true/>
</dict>
Configure maximum number of concurrent tasks: the plugin allows 3 download tasks running at a moment by default (if you enqueue more than 3 tasks, there're only 3 tasks running, other tasks are put in pending state). You can change this number by adding following codes to your Info.plist file.
<!-- changes this number to configure the maximum number of concurrent tasks -->
<key>FDMaximumConcurrentTasks</key>
<integer>5</integer>
Localize notification messages: the plugin will send a notification message to notify user in case all files are downloaded while your application is not running in foreground. This message is English by default. You can localize this message by adding and localizing following message in Info.plist file. (you can find the detail of Info.plist localization in this link)
<key>FDAllFilesDownloadedMessage</key>
<string>All files have been downloaded</string>
In my Flutter Web application I am retrieving values from the map timeslots in Firestore.
This is what the data looks like:
But, instead of retrieving the whole list of values, I get a truncated list like this:
[Mo-Washing-(09:00-10:00, 10:00-11:00, 11:00-12:00, ..., 20:00-21:00, 21:00-22:00)]
Below I have included the 2 functions responsible for retrieving the data and adding it to the list object
static List object = [];
static Map<String, dynamic> timeDetails = {};
static Map<String, dynamic> userDetails = {};
checkExists(docuID) async {
return await firestore()
.collection('environments')
.doc(docuID)
.get()
.then((val) {
userDetails.addAll(val.data());
}).whenComplete(() async {
fs.DocumentSnapshot snapShot = await firestore()
.collection('environments')
.doc(docuID)
.collection('Washing')
.doc('monday')
.get();
if (snapShot == null || !snapShot.exists) {
print('does not exist');
} else {
await getData(docuID, 'Washing');
}
setState(() {});
});
}
getData(docuID, machineName) async {
return await firestore()
.collection('environments')
.doc(docuID)
.collection(machineName)
.doc('monday')
.get()
.then((val) {
timeDetails.addAll(val.data());
}).whenComplete(() {
object.add('Mo-$machineName-${timeDetails['timeslots'].values}');
print(object);
setState(() {});
});
}
This also happens in debugPrint. Would anyone know why this is happening and how I could solve it? Any help on this would be appreciated!
Neither the workaround as mentioned on Github nor debugPrint worked for me, but I managed to solve this by adding .toList() to my getData function:
getData(docuID, machineName) async {
return await firestore()
.collection('environments')
.doc(docuID)
.collection(machineName)
.doc('monday')
.get()
.then((val) {
timeDetails.addAll(val.data());
}).whenComplete(() {
//toList() is added here to .add
object.add('Mo-$machineName-${timeDetails['timeslots'].values.toList()}');
print(object);
setState(() {});
});
}
Output:
[Mo-Washing-[09:00-10:00, 10:00-11:00, 11:00-12:00, 12:00-13:00, 13:00-14:00, 14:00-15:00, 15:00-16:00, 16:00-17:00, 17:00-18:00, 18:00-19:00, 19:00-20:00, 20:00-21:00, 21:00-22:00]
I store all API data to cache. some APIs have more than 10000 data. Postman response time is within one second. but in application very slow to navigate to next page. I used this code:
onPressed: () async {
...
}
else {
var token = Token(
id: 1,
token: tokens,
refreshToken: model.data.refreshToken,
);
await storeRegister(_url,tokens);
await storeEquipmentReg(_url,tokens);
await storeSyncLogin(_url,tokens);
await HelperDefCatMaster().deleteDefCatMaster();
await storeDefCatMaster(_url,tokens);
await HelperDefRegisterCat().deleteDefRegisterCat();
await storeDefRegisterCat(_url,tokens);
await HelperDefCatMaster().deleteDefCatRelation();
await storeDefCatRelation(_url,tokens);
await HelperDefCatMaster().deleteWoDescription();
await storeWoDescription(_url,tokens);
await HelperDefCatMaster().deleteCategoryDefect();
await storeCategoryDefect(_url,tokens);
await storeWorkSource(_url,tokens);
await storeWorkTypes(_url,tokens);
await storePriorities(_url,tokens);
await Helper().insert(token);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ListPage(model.data.token)));
}
storePriorities function look like,
storePriorities(String url, String token) async {
final response = await http.get(
'${url}/v1.0/Priorities',
headers: {'Authorization': 'Bearer ${token}'},
);
final jsonResponse = json.decode(response.body);
Priorities model = Priorities.fromJson(jsonResponse);
int length = model.data.length;
for (int i = 0; i < length; i++) {
var data = DataPriorities(
i: model.data[i].i,
d: model.data[i].d,
);
await HelperDefCatMaster().insertPriorities(data);
}
}
I have given the first answer that suggests to use await only when it's needed.
Well if you are inserting too much data in SQLite I assume that you might be using something like this:
for (int i = 0; i <= 1000; i++) {
db.insert('table_name', dataObject.toMap());
}
Well this will do a lot many transactions at a time and it will consume a lot's of your time.
Change this to something like this and it will increase the speed of inserting data:
Batch batch = db.batch();
for (int i = 0; i <= 1000; i++) {
batch.insert('table_name', dataObject.toMap());
}
await batch.commit();
What we are doing here is that, in single transaction we are doing multiple inserts at a time.
I made this change in my demo project where I was inserting 1000 row at a time and results were great. db.insert when called 1000 times took 7 secs where as batch.insert took less than 1 sec for inserting same amount of data.
If you optimize your code with this solution and use await when needed you should not face any problem on UI. Let me know if this helps.
You are using await keyword to fetch data from SQLite.
And you are fetching a lots of data.
This will make data fetching synchronous, and will affect your UI.
If it is convenient for your use-case to fetch data asynchronously then you can use the following way:
Change :
await Helper().insert(token);
Navigator.push(
context,MaterialPageRoute(builder: (context) => ListPage(model.data.token)));
to :
Helper().insert(token).then((onValue) {
Navigator.push(context,MaterialPageRoute(
builder: (context) => ListPage(model.data.token),
),
);
}
Note: Make your insert method return Future<'token's return type'>
Now use this way for all other await calls.
In my Electron App I would like to inject data (like Fixtures) when App launched.
I use typeorm library for managing my SQLite3 Database connection.
I created json file that represent Entity typeorm and I would like persist all of them in my DB with typeorm. For that It seems that use trasaction is more efficient.
I try two differents things but the result is the same and I don't uderstand why. The issue message is :
Error: Transaction already started for the given connection, commit
current transaction before starting a new one
My first implementation of transaction :
async setAll(entity, data)
{
let connection = await this.init()
const queryRunner = connection.createQueryRunner()
await queryRunner.connect()
for (const [key, value] of Object.entries(data))
{
await typeorm.getManager().transaction(transactionalEntityManager =>
{
})
}
}
My second implementation of transaction :
async setAll(entity, data)
{
let connection = await this.init()
const queryRunner = connection.createQueryRunner()
await queryRunner.connect()
for (const [key, value] of Object.entries(data))
{
let genre1 = new Genre()
genre1.name = 'toto'
genre1.identifier = 'gt'
genre1.logo = ''
genre1.isActivate = false
await queryRunner.startTransaction()
await queryRunner.manager.save(genre1)
await queryRunner.commitTransaction()
await queryRunner.release()
}
}
NB : The second implementation persist correctly the first object but not the others.
How can manage many typeorm Transaction created into loop for persist lot of data ?
async setAll(entity, data) {
let connection = await this.init()
const queryRunner = connection.createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction()
try {
for await (const [key, value] of Object.entries(data)) {
let genre1 = new Genre()
genre1.name = 'toto'
genre1.identifier = 'gt'
genre1.logo = ''
genre1.isActivate = false
const newGenre= queryRunner.manager.create(Genre,genre1)
await queryRunner.manager.save(newGenre)
}
await queryRunner.commitTransaction()
} catch {
await queryRunner.rollbackTransaction()
} finally {
await queryRunner.release()
}