fetching user data from firebase and storing it in static variables - firebase

i am new to flutter and firebase development, so i really don't know how much will it cost me to keep fetching user data from firebase in every screen that i need them in, so i decided to fetch them once and store them in class MyUser static variables as follows:
in MyApp class:
bool isAuthenticated = false;
Future checkAuthenticity() async {
AuthService.getCurrentUser().then((user) async {
if (user != null) {
String myUid = await AuthService.getCurrentUID();
await MyUserController().getCurrentUserFromFirebase(myUid);
if (mounted)
setState(() {
isAuthenticated = true;
});
} else {
if (mounted)
setState(() {
isAuthenticated = false;
});
}
});
}
#override
Widget build(BuildContext context) {
home: isAuthenticated ? Home(passedSelectedIndex: 0) : Register(),
}
from the above code, this line await MyUserController().getCurrentUserFromFirebase(myUid); is as follows:
getCurrentUserFromFirebase(String uid) async {
await FirestoreService().getCurrentUserData(uid);
}
from the above code, this line await FirestoreService().getCurrentUserData(uid); is as follows:
Future getCurrentUserData(String uid) async {
try {
var userData = await FirebaseFirestore.instance.collection('users').doc(uid).get();
MyUser.fromData(userData.data());
} catch (e) {
if (e is PlatformException) {
return e.message;
}
return e.toString();
}
}
from the above code, this line MyUser.fromData(userData.data()); is a constructor in
MyUser class as follows:
class MyUser {
static String uid;
static String name;
static String username;
static String email;
static String userAvatarUrl;
static String location;
static String phoneNumber;
MyUser.fromData(Map<String, dynamic> data) {
uid = data['id'];
name = data['name'];
username = data['username'];
email = data['email'];
userAvatarUrl = data['userAvatarUrl'];
location = data['location'];
phoneNumber = data['phoneNumber'];
}
}
and to make use of all of the following, in each page that i need to load the current user data in, i use for example:
var userId = MyUser.uid
or to show the current user name i use Text('${MyUser.name}');
when i close the app completely and relaunch it again, it should check for authenticity, and complete executing the rest of the code in main() function.
so my questions are:
1) does this have any performance issues when we release the app?
2) does this will really will prevent unnecessary reads that i can consume in every page i need the data in ?
3) is there any better approach to prevent unnecessary reads from firebase, for example to save the current user data as strings and a profile image locally?
pardon me for prolonging the question, but i wanted to share the code itself.
any help would be much appreciated.

As a short answer,
You can make a class of SharedPreferences to store data as strings in key: value manner.
So anywhere you want you can get an instance of that class and reach it from anywhere in the app.
If you also declare some functions which will decode string to json you will get a ready user class instance in return of your function which will make it easier.
So when you want to save user info to Local Storage(SharedPreferences) you may use a function which will encode your User object to string and save it to SharedPreferences as below..
user.dart' as theUser; for conflict issues
class SharedPrefs {
static SharedPreferences _sharedPrefs;
init() async {
if (_sharedPrefs == null) {
_sharedPrefs = await SharedPreferences.getInstance();
}
}
dynamic get user=> _sharedPrefs.getString('user')!=null?theUser.User.fromString(_sharedPrefs.getString('user')):null;
set user(theUser.User user)=> _sharedPrefs.setString('user', jsonEncode(user));
String get accessToken=> _sharedPrefs.getString('access_token');
set accessToken(String accessToken)=> _sharedPrefs.setString('access_token', accessToken);
void removeString(String entry){
_sharedPrefs.remove(entry);
}
}
final sharedPrefs = SharedPrefs();
And in the app anywhere you can use it directly by typing sharedPrefs.user

Related

Run a Task without killing

I'm trying to implement a functionality where user can simply click on download button and can go on any page that he/she wishes while download is still running, and it should not stop.
I'm using xamarin form.
Is there any way to do this, please suggest or any reference, since i couldn't find the same.
Since the class App always exists in memory when the application is running , we can place the Task in App.
Simple Task
//App
public async void DoSomething()
{
await Task.Delay(30000);
}
// in specific page
string result = await (App.Current as App).DoSomething();
Task with return values
//App
public async Task<string> DoSomething()
{
await Task.Delay(30000);
return "123";
}
// in specific page
string result = await (App.Current as App).DoSomething();
If you don't want to put the code into App , we can create a extra class in App to handle this .
//App
private MyTask _myTask;
public MyTask myTask
{
get
{
return _myTask ?? new MyTask();
}
}
//MyTask
public class MyTask
{
public async Task<string> DoSomething()
{
await Task.Delay(30000);
return "123";
}
}
//in specific page
string result = await (App.Current as App).myTask.DoSomething();

How to cache images from Firebase in Flutter?

I'm trying to access images in Firebase Storage and cache them locally on the device.
My current attempt uses flutter_cache_manager. The documentation states:
Most common file service will be an [HttpFileService], however one can also make something more specialized. For example you could fetch files from other apps or from local storage.
class HttpFileService implements FileService {
http.Client _httpClient;
HttpFileService({http.Client httpClient}) {
_httpClient = httpClient ?? http.Client();
}
#override
Future<FileServiceResponse> get(String url,
{Map<String, String> headers = const {}}) async {
final req = http.Request('GET', Uri.parse(url));
req.headers.addAll(headers);
final httpResponse = await _httpClient.send(req);
return HttpGetResponse(httpResponse);
}
}
I've tried to extend this class to process the URL for Firebase
class FirebaseHttpFileService extends HttpFileService {
#override
Future<FileServiceResponse> get(String url, {Map<String, String> headers = const {}}) async {
var ref = FirebaseStorage.instance.ref().child(url);
var _url = await ref.getDownloadURL() as String;
return super.get(_url);
}
}
And extend the BaseCacheManager using a template from the GitHub repo, replacing the file service with my new one.
class FirebaseCacheManager extends BaseCacheManager {
static const key = "firebaseCache";
static FirebaseCacheManager _instance;
factory FirebaseCacheManager() {
if (_instance == null) {
_instance = new FirebaseCacheManager._();
}
return _instance;
}
FirebaseCacheManager._() : super(key,
maxAgeCacheObject: Duration(days: 7),
maxNrOfCacheObjects: 20,
fileService: FirebaseHttpFileService());
Future<String> getFilePath() async {
var directory = await getTemporaryDirectory();
return p.join(directory.path, key);
}
}
But I get the following error:
setState() called after dispose(): _ImageState#50d41(lifecycle state: defunct, not mounted, stream: ImageStream#ac6d5(MultiFrameImageStreamCompleter#0c956, [2448×3264] # 1.0x, 3 listeners), pixels: null, loadingProgress: null, frameNumber: null, wasSynchronouslyLoaded: false)
I can process the URL before attempting to retrieve the file but that needlessly wastes time. I've also tried to use other packages like Flutter Cache Image but it seems to crash the app after a short amount of time.
Thanks for any pointers in the right direction!
This problem is actually tied to the errorWidget as seen in the issue here.
The code is working if the error widget is commented out in CachedNetworkImage.

Unable to Return Queried Data From Firebase (Flutter/Dart)

Context: I'm trying to query and return a String (imgUrl) from Firebase. I'm always able to print the string inside the query, but the returned value is always null. I'm wondering if my query is wrong and am not sure what best practices are.
Database Outline:
Query Function:
This is the code under our DatabaseService() class, which contains all database queries and updating functions.
String getImageUrl(String _uid) {
String _imgUrl;
Firestore.instance
.document('users/$_uid')
.get()
.then((value) => _imgUrl = value['imgUrl']);
return _imgUrl;
}
Main:
getImageUrl() is called under setImage(). The toast under setImage always returns null and so does the code under it.
String _uid;
// Sets variable '_uid' to the uid of the current user
// Gets called in initstate
Future _getUid() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
_uid = user.uid;
}
// Sets the profile photo. If there is no existing profile photo online,
// grab the image on the device. If there is no image online OR on the device,
// Display the default image
void setImage(String url) {
// Get the url that's stored in the db
String _tempUrl = DatabaseService().getImageUrl(_uid); // always ends up being null
Fluttertoast.showToast(msg: "_tempUrl: $_tempUrl");
// Rest of the function
}
#override
void initState() {
super.initState();
_getUid();
}
Please let me know what to do to fix this as it's driving me crazy. Thanks in advance.
Change the method to the following:
Future<String> getImageUrl(String _uid) async {
String _imgUrl;
DocumentSnapshot value =
await Firestore.instance.document('users/$_uid').get();
_imgUrl = value['imgUrl'];
return _imgUrl;
}
use async/await to wait for the future to finish, and then call it like the following:
void setImage(String url) async{
// Get the url that's stored in the db
String _tempUrl = await DatabaseService().getImageUrl(_uid); // always ends up being null
Fluttertoast.showToast(msg: "_tempUrl: $_tempUrl");
// Rest of the function
}

How to refresh claim of a different user than the one logged in during the current request?

I use the following code to update the claims of a user in my web application. However, to update the claims/cookie of this user, I want to force him to login again. So essentially I want to expire his cookie after I update the claims. Any idea how to do this?
await _signInManager.RefreshSignInAsync(user); is the first thing I tried, but fails because I'm updating the claims of another user (the one that is currently logged in) :)
All other examples I found are more or less the same as RefreshSignInAsync and do not deal with the fact that I'm updating the claims of another user.
public async Task<IActionResult> AddClaimPost(string id)
{
var user = _context.ApplicationUser
.SingleOrDefault(m => m.Id == id);
foreach(var item in Request.Form)
{
if (item.Key.Contains("Claim"))
{
if (item.Value.Contains("true"))
{
if (!User.HasClaim(item.Key, item.Key))
{
var result = await _userManager.AddClaimAsync(user, new Claim(item.Key, item.Key));
}
}
else
{
var result2 = await _userManager.RemoveClaimAsync(user, new Claim(item.Key, item.Key));
}
}
}
await _signInManager.RefreshSignInAsync(user);
return RedirectToAction("Overview");
}
After searching a few days I discovered that what I want is not possible. You cannot force logging the user out without putting the cookie timespan to 0
options.Cookies.ApplicationCookie.ExpireTimeSpan = 0;
In this case it will check the cookie every time the user makes a request. With the following code you can than force the user to login again:
await _userManager.UpdateSecurityStampAsync(user);
I don't recommend the 0 expire timespan approach.
If you have a redis server (or any other persistent data store that is performant) you can do something like:
await redis.StringSetAsync("refresh_login_" + user.Id, "1", null);
Then on every page load you will check this redis value and refresh the signin if the key is set for you:
Filters/BaseActionFilter.cs:
public class BaseActionFilter: IAsyncActionFilter, IAsyncPageFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{ // For classic controllers
await PerformPageTasks(context.HttpContext);
await next();
}
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context,
PageHandlerExecutionDelegate next)
{ // For razor pages in Areas/
if ((context.HandlerInstance is PageModel page))
{
await PerformPageTasks(context.HttpContext);
}
await next.Invoke();
}
public async Task PerformPageTasks(HttpContext context)
{
var signinManager = context.RequestServices.GetService<SignInManager<MyWebUser>>();
if (signinManager.IsSignedIn(context.User))
{
var cache = context.RequestServices.GetService<IDistributedCache>();
var redis = (await ((RedisCache)cache).GetConnectionAsync()).GetDatabase();
var userManager = context.RequestServices.GetService<UserManager<MyWebUser>>();
var user = await userManager.GetUserAsync(context.User);
if ((await redis.StringGetAsync("refresh_login_" + user.Id)) == "1")
{
await redis.KeyDeleteAsync("refresh_login_" + user.Id);
// refresh the user
await signinManager.RefreshSignInAsync(user);
}
}
}
public async Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
await Task.CompletedTask;
}
}
Startup.cs:
services.AddMvc(o =>
{
...
o.Filters.Add(new BaseActionFilter());
}).AddHybridModelBinder();
If you only use traditional controllers or Areas/ razor pages then you can adapt the code accordingly.
Note this requires the user to make an additional page load before the claims are set, so for things like [Authorize] you would need to put this code earlier in the chain and I'm not sure exactly how to do that.

usermanager.addtorole - An asynchronous module or handler completed while an asynchronous operation was still pending

I am adding a user to my aspnetusers database and that is working fine. Then I am also trying to link them to an existing role. That is when I get the error: "An asynchronous module or handler completed while an asynchronous operation was still pending."
Here is my method with the problem code:
private async void checkOldDB(string email, string password)
{
bool isValidUser = false;
ReportsMvc.App_Code.BLL.FUN.cFunUser user = ReportsMvc.App_Code.DAL.FUN.cFunUserDB.getUser(email);
if (user != null)
{
isValidUser = PasswordHash.PasswordHash.ValidatePassword(password, user.Password);
if (!isValidUser)
{
isValidUser = PasswordHash.PasswordHash.ValidateHashes(password, user.Password);
}
}
if (isValidUser)
{
var user2 = new ApplicationUser { UserName = email, Email = email };
var result = await UserManager.CreateAsync(user2, password);
if (result.Succeeded)
{
string role = user.Role;
if (string.IsNullOrEmpty(role))
{
role = "User";
}
UserManager.AddToRole(user2.Id, role);
await SignInManager.SignInAsync(user2, isPersistent: false, rememberBrowser: false);
}
}
}
The line starting with "await SignInManager" was working fine. Then when I added in that code to AddToRole, I started getting the above error. This identity/authentication stuff is all very new to me.
You should change async void to async Task and await it where you call it.
As a general rule, you should avoid async void; it should only be used for event handlers. I describe this more in an MSDN article.

Resources