Flutter - Storing Authenticated User details - flutter

I am new to Flutter and I am writing an app which requires users to login. I already have an API that provides me with a JWT. I then store this using the secure storage library.
Once the user gets the 200 OK with the token, I want to send another request to the server to retrieve the user's details and store it in a kind of singleton class, so I can use it through out the app until he logs out. There are so many buzzwords being thrown at me right now, Providers and BLoCs.
I was wondering what is the best practice to store the current logged in user details in the app? Previously I've been aware of storing the data in a singleton class. But now I'm not sure whether I should write a singleton class or a Provider? (I'm still rusty on my knowledge about how Providers and BLoCs work).

Start Simple
A simple starting point to store the data, which i assume is the form of key/value pairs, would be using shared preferences.
What are Shared Preferences
The main use of Shared Preferences is to save user preferences, settings, maybe data (if not too large) so that next time the application is launched, these pieces of information could be retrieved and used.
How to use
Set an instance variable of Shared Preferences and store the value with a key identifier.
Any time you want to retrieve the value when the app starts, just get it using the key. See example below of setting and getting a stored username.
Future<bool> setUserName(String value) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.setString("username",value);
}
Future<String> getUserName(String value) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString("username");
}
Installation
To installed shared preferences, add this to your package's pubspec.yaml file:
dependencies:
shared_preferences: ^0.5.7+3
More information on the package page.

Solution 1
This is how my app stores data:
Create a new dart file for storing "global" variables like so, and store all the "session" data there:
class SessionData{
String encryptedUserId;
String encryptedPassword;
String encryptedToken;
int userId;
}
SessionData globalSessionData;
//Having a clear function is pretty handy
void clearSessionData(){
globalSessionData = new SessionData();
}
And if you named the file for example global.dart import it, read and write nonpermanent data to it (persist until the app is closed):
import 'global.dart' as global;
global.globalSessionData = ...;
//If you want to clear it:
global.clearSessionData();
Solution 2
If you want to persist the data even if the application closes there are two ways to do that:
Solution 2a
Use sharedPrefences to store and retrieve key-data values on the user's device:
import 'package:shared_preferences/shared_preferences.dart';
final SharedPreferences prefs = await SharedPreferences.getInstance();
//Write different types of data
await prefs.setBool("male", true);
await prefs.setDouble("key", 7.0);
await prefs.setString("username", "Mr. User");
//Retrive different types of data
prefs.getBool("male");
prefs.getDouble("key");
prefs.getString("username");
Solution 2b
Use sqflite to store advanced data structures, as it is a fully-featured database.
Flutter has a great tutorial on using SQLite with flutter. Although if you go with this type of solution you must know a little SQL.

Related

Sharing a text file (csv) with share_plus: "unable to attach file"

I want to get data from Firestore, convert it into csv format, then share it using the user's method of choice (e.g. email). I'm using the csv package to convert the data to csv, the path_provider to get the directory to write the file to the phone, and the share_plus package to share the file.
However, when I tap a share method (e.g. Gmail, Outlook, Whatsapp), it opens the right app but then I get a message on the phone like "Unable to attach file" (but there is no error in my app). The file is definitely being written as I can read it and it comes back with the expected string. The ShareResultStatus that is returned by the share is 'successful' Can anyone figure it out what the problem is and how to fix it?
Here is my code:
Future exportData() async {
// Dummy data (in reality, this will come from Firestore)
List<List> dummyData = [
['Date', 'Piece', 'Rating'],
[DateTime.utc(2022, 05, 01), 'Sonata', 4],
[DateTime.utc(2022, 05, 02), 'Sonata', 2],
];
// Turn into CSV
String csvData = _convertToCsv(dummyData);
// Write to local disk
await _writeData(csvData);
// Share
String filePath = await _filePath;
final result =
await Share.shareFilesWithResult([filePath], mimeTypes: ['text/csv']);
print(result.status);
}
And here are the functions used in above code:
Future<String> get _filePath async {
final directory = await getApplicationDocumentsDirectory();
return '${directory.path}/my_practice_diary_data.csv';
}
Future<File> _writeData(String csvData) async {
String filePath = await _filePath;
final file = File(filePath);
return file.writeAsString(csvData);
}
String _convertToCsv(List<List> data) {
String result = const ListToCsvConverter().convert(data);
return result;
}
Note: I've tried doing it both as txt and csv files, got same result
*** EDIT (06/06/2022): After a lot of reading and watching youtube videos, I have come to realise that the problem is that the directory getApplicationDocumentsDirectory() is only accessible by my app (and hence the app that is is being shared to, like gmail, cannot read it).
For now I have worked around it by using the package mailer and using user's google credentials to send emails (I followed these helpful youtube tutorials: Flutter Tutorial - How To Send Email In Background [2021] Without Backend and Flutter Tutorial - Google SignIn [2021] With Firebase Auth - Android, iOS, Flutter Web.
However, it would still be nice to know a nice way to generate a file and share it using share_plus, as per my original question. I believe that this can be achieved via one of two ways:
Find a way to allow other apps to access this specific file in the app directory; or
Find a way to download the file into an external storage (like downloads folder), then share that. (I cannot find a way to do this on both Android and iOS).
Anyone who knows how to do these or any other solution to the problem, please share!

For PWA, what is the easiest way to get per-device settings (as in reading a .ini file or environment variables)?

For PWA, what is the easiest way to get per-device settings (as in reading a .ini file or environment variables)?
I'm making a very simple in-company react PWA for andriod-based tablets (only). I just want to store a couple of settings (the room number where the device is being used, and a device id) and read those in upon startup.
My experience in recently in Windows, and so I'm imaging a text file that I could place on each tablet with the settings. Does that make sense for our PWA?
Or is there a better/easier way to do app settings?
Thank you.
The answer depends on how that data is initially provisioned and what kind of guarantees you need about it being "tamper-proof."
Assuming you can provision the information during the web app's initial launch, and you're fine using storage that's exposed via a browser's Developer Tools (i.e. your threat model doesn't include a motivated user using DevTools to erase/modify the data), a simple approach would be to a) use the Cache Storage API to read/write that data as JSON, using a synthetic URL as the key and b) requesting persistent storage just for an added guarantee that it won't be purged if the device ends up running low on storage.
This could look like:
// Just use any URL that doesn't exist on your server.
const SETTINGS_KEY = '/_settings';
const SETTINGS_CACHE_NAME = 'settings';
async function getSettings() {
const cache = await caches.open(SETTINGS_CACHE_NAME);
const cachedSettingsResponse = await cache.match(SETTINGS_KEY);
if (cachedSettingsResponse) {
return await cachedSettingsResponse.json();
}
// This assumes a generateInitialSettings function that does provisioning
// and returns an object that can be stringified.
const newSettings = await generateInitialSettings();
await cache.put(SETTINGS_KEY, JSON.stringify(newSettings), {
headers: {
'content-type': 'application/json',
}
});
// Optional: request persistent storage.
// This call may trigger a permissions dialog in the local browser, so it is
// a good idea to explain to the user what's being stored first.
// See https://web.dev/persistent-storage/#when-should-i-ask-for-persistent-storage
if (navigator.storage && navigator.storage.persist) {
// This returns a promise, but we don't have to delay the
// rest of the program execution by await-ing it.
navigator.storage.persist();
}
return newSettings;
}

Is there a way I can translate my flutter app for free?

I am making a flutter app that fetches data from a database using an API. The client wants me to add multilanguage support, so is there a way with which I can change the language of the whole app. Also the data that is being fetched from database needs to be translated. So it would be better if the data is translated before being displayed.
Thank you.
The best approach would be to get translated data from the backend as it will be a lot faster compared to translating the text at the mobile end.
There is this translator package which you can use to convert the data fetched from the backend.
However, note that your app will become slow when you implement this because you will have to wait for two API calls:
For fetching data from the backend
Uploading this fetched data & then getting translations back
The translator package makes use of Google Translator to translate text into any language that you want.
void main() async {
final translator = GoogleTranslator();
final input = "Здравствуйте. Ты в порядке?";
translator.translate(input, from: 'ru', to: 'en').then(print);
// prints Hello. Are you okay?
var translation = await translator.translate("Dart is very cool!", to: 'pl');
print(translation);
// prints Dart jest bardzo fajny!
print(await "example".translate(to: 'pt'));
// prints exemplo
}
The simple use case example mentioned in the package.

Saving Cache from api response on flutter

I Would like to seek advice for a simple way to cache this response from my API on flutter. What I am intending to do is when there no internet available it will use the cache item and if there is internet available it will update the cache to the latest one.The code below is the current API call that I am using.
_getCurrentPlanDetails() async{
var url = "http://dmp-portal-fixture.herokuapp.com/getCurrentPlan";
var response = await http.get(url);
if (response.statusCode == 200){
var jsonResponse = convert.jsonDecode(response.body);
setState(() {
currentPlan = jsonResponse["data"]["CurrentPlan"];
cutOffDate = jsonResponse["data"]["CutoffDate"];
contractExpiry = jsonResponse["data"]["ContractExpiry"];
monthsRemaining =jsonResponse["data"]["MonthsRemaining"];
});
print("get Current plan");
}else{
print("error current plan");
}
}
There are multiple packages that handle this depending on the use-case of your requirements:
localstorage which is a JSON based storage.
shared_preferences for storing key-value pairs.
flutter_secure_storage for storing all your access token and security related components of the app such as all session information, user credentials etc...
SQL database there is a quite extensive tutorial on storing files in a databese in Flutter, so I suggest reading that one as well in case you need to preserve a lot of app related data in case no internet is available.

Flutter: Provider: How to handle model dependencies?

I'm looking for suggestions on how to handle loading things after login, and combine data from multiple endpoints without mixing too much stuff, specifically the following two things:
1) Login flow
After I get a successful login response with userId, I need to push HomeScreen() and load some initial data, from various providers.
Example:
// home_screen.dart
initState() {
super.initState();
initializeData();
}
Future <void> initializeData() {
var authenticationProvider = Provider.of<AuthenticationProvider>(context);
var accountProvider = Provider.of<AccountProvider>(context);
var albumProvider = Provider.of<AlbumProvider>(context);
var songProvider = Provider.of<SongProvider>(context);
await accountProvider.loadAccount(authenticationProvider.getLoggedInUser().id);
await albumProvider.loadAlbums();
await songProvider.loadSongsForAlbums(albumProvider.getAlbums())
}
This works, but feels ugly?
2) Cleaner data sharing
Imagine the API like this:
api/albums (model contains info about album)
api/albumpurchases (model contains which albumId and userId)
What would be the best way to get purchased albums of the logged in user?
I can think of 3 different ways, none of which seem good:
AlbumProvider having two arrays, albums[], and purchases[], and a method getAlbumByPurchase (String purchaseId) or getPurchaseForAlbum(String albumId) which then does .where() by Id and returns the item.
Having AlbumProvider and AlbumPurchaseProvider, then using ProxyProvider to combine the two.
I'm not sure how exactly would that be implemented, an example would be very appreciated!
Adding purchase property to an Album, then manually mapping it similarly to way #1
.
I've used this so far, and it seems great in the beginning but gets very ugly very quick since your subsequent Album HTTP responses everywhere would be lacking that purchase property, so I either need to get the purchase again, or I need to get it an Album object with the purchase from AlbumProvider based on an Id in response I got.