Flutter user data taking up a lot of space - flutter

My flutter app user data takes up a lot of space. I'm currently using the following code to save the user data
class FileUtil {
static Future<String> get getFilePath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
static Future<File> get getFile async {
final path = await getFilePath;
return File('$path/user.txt');
}
static Future<File> saveToFile(String data) async {
final file = await getFile;
return file.writeAsString(data);
}
static Future readFromFile() async {
try {
final file = await getFile;
String fileContents = await file.readAsString();
log(fileContents);
return json.decode(fileContents);
} catch (e) {
return "";
}
}
String formatData() {
String formattedString;
Map x = {};
x['a'] = a;
// other variables
formattedString = json.encode(x);
return formattedString;
}
void saveData() async {
try {
await saveToFile(formatData());
//print('DATA SAVED');
} catch (e) {
//print('Could not save data due to: $e');
}
}
}
Whenever the user interacts with something in the app that needs to be saved, I run saveData(). This happens quite often in my app. However, after using the app for a while, the user data can jump to a few hundred MB. I've used a JSON calculator to estimate the space of the formatData() output string and it's much less than 1MB. What should I do to minimise user data?

Related

Delete file permanantly from list view in flutter

I list all pdf files from storage and now I want to delete multi-files in my flutter list . as well as from the device file manager. I am using this function but when I delete and restart the app the file comes again
This is the function I'm using to delete the list:
void deleteItems() {
var list = myMultiSelectController.selectedIndexes;
list.sort((b, a) => a.compareTo(b));
list.forEach((element) {
files.removeAt(element);
});
setState(() {
myMultiSelectController.set(files.length);
});
}
files.removeAt(element); Just removes file from the list. You need to actually delete file from device.
eg
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
print('path ${path}');
return File('$path/counter.txt');
}
Future<int> deleteFile() async {
try {
final file = await _localFile;
await file.delete();
} catch (e) {
return 0;
}
}
See more here from SO answer

Firebase: How do I write to Realtime Database only if the write to Cloud Storage was successful?

The first function will write to Firebase Cloud Storage and if there are no errors, I want to write to Realtime Database. Both of these are "fire and forget" functions, but I only want to run the second one if the first one successfully uploaded the file to Cloud Storage.
void sendFile(ChatData file, String filepath, String filename) async {
saveToCloudStorage(filepath, filename);
//If the aformentioned function was successful, run the next one
saveToRTDB(file);
}
void saveToCloudStorage(String filepath, String filename) async {
_firebaseStoragePath = MyAppStorageDir + filename;
File file = File(filepath);
try {
await _firebaseStorage
.ref(_firebaseStoragePath)
.putFile(file);
} catch (e) {
}
}
void saveToRTDB(ChatData file) async {
_messagesRef.push().set(file.toJson());
}
Since your saveToCloudStorage swallows any exceptions that are raised, there is no way for the caller to determine whether the call succeeded or failed.
If you don't want to expose what error might have happened, you can return an (async) boolean:
Future<bool> saveToCloudStorage(String filepath, String filename) async {
_firebaseStoragePath = MyAppStorageDir + filename;
File file = File(filepath);
try {
await _firebaseStorage
.ref(_firebaseStoragePath)
.putFile(file);
} catch (e) {
return false;
}
return true;
}
And then in the caller:
saveToCloudStorage(filepath, filename).then(() {
saveToRTDB(file);
})

read file returns null Flutter

I have a page that writes a color on file, called "colors.txt".Then the page is closed, when it will be opened again this file will be read and its content (String) printed on the screen.
This is the class that handles reads and writes :
class Pathfinder {
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/colors.txt');
}
Future<File> writeColor(String color) async {
final file = await _localFile;
// Write the file
return file.writeAsString('$color');
}
Future<String> readColor() async {
try {
final file = await _localFile;
// Read the file
final contents = await file.readAsString();
return contents;
} catch (e) {
// If encountering an error, return 0
return "Error while reading colors";
}
}
}
Before page closure, the color has been saved with writeColor, we just need to read the file and print its content.
And this is how I read the color :
void initState() {
super.initState();
String colorRead;
() async {
pf = new Pathfinder();
colorRead = await pf.readColor();
}();
print("Color in initState: " + colorRead.toString());
}
The problem is that colorRead is always null. I already tried .then() and .whenCompleted() but nothing changed.
So my doubt is :
Am I not waiting read operation in right way or the file, for some reasons, is deleted when page is closed?
I think that if file wouldn't exists then readColor should throw an error.
EDIT : How writeColor is called :
Color bannerColor;
//some code
await pf.writeColor(bannerColor.value.toRadixString(16));
void initState() {
super.initState();
String colorRead;
() async {
pf = new Pathfinder();
colorRead = await pf.readColor();
}();
print("Color in initState: " + colorRead.toString()); /// << this will execute before the async code in the function is executed
}
It's null because of how async/await works. The print statement is going to be called before the anonymous async function finishes executing. If you print in inside the function you should see the color if everything else is working correctly.

PlatformException error, Invalid document reference, When attempting to work with Firebase Storage

I have created a function to work on my app. This function add's the photo from my camera or gallery into the Firebase storage, and into the user collection. Althought I'm receiving a strange error when trying to add the data. I have attempted to pass throught this Exception but the data wasn't added neither.
The erro:
This is the function:
class Product {
final Firestore firestore = Firestore.instance;
final FirebaseStorage storage = FirebaseStorage.instance;
DocumentReference get firestoreRef => firestore.document('products/$id');
StorageReference get storageRef => storage.ref().child('products').child(id);
Future<void> save() async {
loading = true;
final Map<String, dynamic> data = {
'name': name,
'description': description,
'sizes': exportSizeList()
};
if (id == null) {
final doc = await firestore.collection('products').add(data);
id = doc.documentID;
} else {
await firestoreRef.updateData(data);
}
final List<String> updateImages = [];
for (final newImage in newImages!) {
if (images.contains(newImage)) {
updateImages.add(newImage as String);
} else {
final StorageUploadTask task =
storageRef.child(Uuid().v1()).putFile(newImage as File);
final StorageTaskSnapshot snapshot = await task.onComplete;
final String url = await snapshot.ref.getDownloadURL() as String;
updateImages.add(url);
}
}
for (final image in images) {
if (!newImages!.contains(image)) {
try {
final ref = await storage.getReferenceFromUrl(image);
await ref.delete();
} catch (e) {
debugPrint('Falha ao deletar $image');
}
}
}
await firestoreRef.updateData({'images': updateImages});
images = updateImages;
loading = false;
}
}
From the error message is looks like id doesn't have a value in this call:
firestore.document('products/$id');
When id has no value, that leads to a document reference with a path /products/, which explains the error message.
So you'll want to run the code in a debugger, set a breakpoint on that line, and figure out why id doesn't have a value at that point.

Document access during initialisation issue on Android

I am having trouble accessing files saved in getApplicationDocumentsDirectory on android only, I keep locale, theme and assets files stored there and when booting up the app those files are always inaccessible and as a result the app does not load, I tried using shared_prefs to track a first load scenario to use the files in assets/ and then later use the ones download but that seems to be an unreliable fix as at 2nd run they also inaccessible.
The only thing that seems to work reliable is the theme.json, but both copy functions run through the same helpers so what copies the theme over is what copies the locale over. Theme is loaded up in main.dart but the locale is loaded up with EasylocaLizationDelegate and if the files are in Document storage it can never load them up.
None of these issues are present on iOS, does anyone know of any special tricks to make this work for android, I have tried google but not really sure what to search for.
class FileHelpers {
static Future<Directory> getDownloadDirectory() async {
Directory dir = await getApplicationDocumentsDirectory();
globals.documentDirectory = dir;
return dir;
}
static verifyOrCreateDirectory(String folderName) async {
String path = globals.documentDirectory.path;
final Directory _folder = Directory('$path/$folderName/');
if (await _folder.exists()) {
//if folder already exists return path
return _folder.path;
} else {
//if folder not exists create folder and then return its path
final Directory _createdFolder = await _folder.create(recursive: true);
return _createdFolder.path;
}
}
static void writeStringToFile(String data, String path) {
new File(path).writeAsString(jsonEncode(data));
}
}
class ThemeHelpers {
static final _folderName = 'theme';
static final _fileName = 'theme.json';
static Future<void> loadTheme() async {
String documentPath = globals.documentDirectory.path;
try {
String assetTheme = 'assets/$_fileName';
String documentTheme = "$documentPath/$_folderName/$_fileName";
String loadPath = documentTheme;
/// We need to check if we can use the local assets as Android has some
/// issues using document assets this early on in the app run
if (!globals.canUseLocalAssets) {
loadPath = assetTheme;
}
Box box = await Hive.openBox(globals.environment.entryCode);
String data = await rootBundle.loadString(loadPath);
box.put(HIVE_COLLECTION_THEME, data);
} catch (e) {
print(e);
}
}
/// In order to support offline themes we ship the current version as at build
/// time with the app.
/// This method is to copy the files from the assets folder into the documents
/// directory. This gives us the option to then update when needed.
static copyTheme() async {
String path = await FileHelpers.verifyOrCreateDirectory(_folderName);
String themePath = '$path/$_fileName';
/// Check if the file already exists, if not copy it over
if (FileSystemEntity.typeSync(themePath) == FileSystemEntityType.notFound) {
String assetPath = "assets/$_fileName";
String data = await rootBundle.loadString(assetPath);
await new File(themePath).writeAsString(data);
print('theme copied');
}
}
static updateTheme() async {
String path = await FileHelpers.verifyOrCreateDirectory(_folderName);
var url = ApiEndpoint.uri('/theme').toString();
try {
bool shouldUpdate = await _checkIfUpdated();
if (shouldUpdate) {
/// If theme has been updated download the latest
/// version and store it.
///
/// Currently this happens as a non-blocking action
/// so updates will take effect the next time the user opens the app.
var response = await Session.apiGet(url);
String localePath = '$path/$_fileName';
await new File(localePath).writeAsString(jsonEncode(response));
/// Once updated reload the theme into [Hive]
loadTheme();
}
} catch (error) {
print('Unable to update theme');
}
}
/// This method makes alight call comparing the [__lastUpdated] in our local
/// copy of the Locale to the API version.
static Future<bool> _checkIfUpdated() async {
String assetPath =
'${globals.documentDirectory.path}/$_folderName/$_fileName';
if (FileSystemEntity.typeSync(assetPath) ==
FileSystemEntityType.notFound) throw "File not found";
try {
String data = await rootBundle.loadString(assetPath);
String currentThemeDate = json.decode(data)['__lastUpdated'];
var url = ApiEndpoint.uri(
'/theme/check',
queryParameters: {
"date": currentThemeDate,
},
).toString();
bool response = await Session.apiGet(url);
if (response.runtimeType == ErrorModel) throw response;
return response;
} catch (error) {
print("Unable to check for updated theme: $error");
return Future.value(false);
}
}
}
class LocaleHelpers {
static final _directory = DIRECTORY_LOCALE;
/// In order to support offline locale we ship the current version as at build
/// time with the app.
/// This method is to copy the files from the assets folder into the documents
/// directory. This gives us the option to then update when needed.
static copyLocaleFiles() async {
String path = await FileHelpers.verifyOrCreateDirectory(_directory);
for (var locale in globals.supportedLocale) {
String localeKey = locale.toString();
String localePath = '$path$localeKey.json';
/// Check if the file already exists, if not copy it over
if (FileSystemEntity.typeSync(localePath) ==
FileSystemEntityType.notFound) {
String assetPath = "assets/$_directory/$localeKey.json";
String data = await rootBundle.loadString(assetPath);
FileHelpers.writeStringToFile(data, localePath);
}
}
}
static updateLocale() async {
String path = await FileHelpers.verifyOrCreateDirectory(_directory);
/// Iterate through locale and check each supported
/// language if there is an updated version on the API
for (Locale locale in globals.supportedLocale) {
String localeString = locale.toString();
var url = ApiEndpoint.uri('/locale/$localeString.json').toString();
try {
bool shouldUpdate = await _checkIfUpdated(localeString);
if (shouldUpdate) {
/// If Locale has been updated download the latest
/// version and store it.
///
/// Currently this happens as a non-blocking action
/// so updates will take effect the next time the user opens the app.
var response = await Session.apiGet(url);
String localePath = '$path/$localeString.json';
FileHelpers.writeStringToFile(localePath, jsonEncode(response));
}
} catch (error) {
print('Unable to update locale: $localeString');
}
}
}
/// This method makes alight call comparing the [_sheetToFbDate] in our local
/// copy of the Locale to the API version.
static Future<bool> _checkIfUpdated(String langKey) async {
try {
String assetPath = '${globals.documentDirectory.path}/locale/$langKey.json';
String data = await rootBundle.loadString(assetPath);
var currentLocaleDate = json.decode(data)['_sheetToFbDate'];
var url = ApiEndpoint.uri(
'/locale/check/$langKey',
queryParameters: {
"date": currentLocaleDate,
},
).toString();
bool response = await Session.apiGet(url);
if (response.runtimeType == ErrorModel) throw response;
return response;
} catch (error) {
print("Unable to check for update: $langKey");
return Future.value(false);
}
}
}
I eventually realised that while not an issue on iOS, using rootBundle.loadString does not work for document storage on Android.
I needed to replace that bit with
File file = File('PATH_TO_FILE');
data = await file.readAsString();