Flutter Web Firebase Auth not not persistant - flutter

First of all: I know this is a popular question. It seemed to have been fixed with this but I still logged out after I refresh/hot restart/hot reload my application.
This is how I check it within the go_router:
redirect: (context, state) {
final String destination = state.location;
final bool isOnStartView = destination == '/start';
final bool isOnEmailFlow = state.subloc.contains('/email');
final bool isLoggedIn = AuthService.isLoggedIn();
if (!isOnStartView && !isOnEmailFlow && !isLoggedIn) {
return '/start';
}
return null;
},
And this is from my AuthService:
static final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
static User? get currentUser => FirebaseAuth.instance.currentUser;
static bool isLoggedIn() {
return _firebaseAuth.currentUser != null;
}
This should've been fixed according to flutterfire but I still get logged out. Anyone else experiencing this issue?
My versions:
firebase_core: ^1.20.0
firebase_auth: ^3.6.4
firebase_storage: ^10.3.4
cloud_firestore: ^3.4.7

Related

Can't sign in using google in Flutter

I am able to Google Sign in using web version of my flutter app but can't Google sign in from android app.
This is the error I'm getting:
GraphicExtModuleLoader::CreateGraphicExtInstance false
D/Surface (29460): Surface::connect(this=0x753ab3e000,api=1)
D/Surface (29460): Surface::setBufferCount(this=0x753ab3e000,bufferCount=3)
D/Surface (29460): Surface::allocateBuffers(this=0x753ab3e000)
V/PhoneWindow(29460): DecorView setVisiblity: visibility = 0, Parent = android.view.ViewRootImpl#9837577, this = DecorView#bcd75e4[MainActivity]
V/PhoneWindow(29460): DecorView setVisiblity: visibility = 4, Parent = android.view.ViewRootImpl#9de9d56, this = DecorView#63bb4f5[SignInHubActivity]
D/Surface (29460): Surface::disconnect(this=0x753ab3e000,api=1)
D/View (29460): [Warning] assignParent to null: this = DecorView#63bb4f5[SignInHubActivity]
E/flutter (29460): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 10: , null, null)
Refereing to this Flutter and google_sign_in plugin: PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 10: , null) answer on StackOverflow, I couldn't understand what this answer is trying to say...
However, I do have provided my SHA1 key on firebase. Re-downloaded and replaced the google-services.json in my flutter app, but still can't sign in on Android.
This is my GoogleSignIn code:
onPressed: () async {
await Firebase.initializeApp();
final FirebaseAuth _firebaseAuth =
FirebaseAuth.instance;
final GoogleSignIn _googleSignIn =
GoogleSignIn();
Future<User> _signIn(BuildContext context) async {
debugPrint("1");
final GoogleSignInAccount googleUser =
await _googleSignIn.signIn();
debugPrint("2");
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
debugPrint("a");
final AuthCredential credential =
GoogleAuthProvider.credential(
idToken: googleAuth.idToken,
accessToken: googleAuth.accessToken);
User userDetails = (await _firebaseAuth
.signInWithCredential(credential))
.user;
ProviderDetails providerInfo =
ProviderDetails(userDetails.uid);
List<ProviderDetails> providerData =
<ProviderDetails>[];
providerData.add(providerInfo);
UserDetails details = UserDetails(
userDetails.uid,
userDetails.displayName,
userDetails.email,
userDetails.photoURL,
providerData);
if (details.userName.toString() != '') {
debugPrint("Email ${details.userEmail}");
globals.isLoggedIn = true;
SharedPref prefs = SharedPref();
String photoUrl = details.photoUrl.toString();
prefs.save("photoUrl", photoUrl);
prefs.save("username",
details.userName.toString());
prefs.save(
"email", details.userEmail.toString());
if (mounted) {
setState(() {
inProgress = false;
});
}
} else {
globals.isLoggedIn = false;
debugPrint(
"Check your internet Connection");
}
}
if (mounted) {
setState(() {
inProgress = true;
});
}
await _signIn(context);
debugPrint("LoggedIn");
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Dashboard()),
);
}
Actually I needed to add this line into my app level build.gradle file dependencies section.
implementation 'com.google.android.gms:play-services-auth:19.0.0'
As well needed to link my project from this link too.
https://developers.google.com/identity/sign-in/android/start-integrating#configure_a_project
Might it help someone in future!
Problem is When you registered your app on google console and created clientId, you choose web platform, now you have to create another clientId with Android Platform. You can use same sha1 key.

Flutter Location package requestService() never returns

I have a function like this. Using Location package from flutter it shows the dialog to enable GPS.
Future<bool> _checkServiceStatus() async {
final Location location = Location();
bool serviceStatus = await location.serviceEnabled();
if (!serviceStatus) {
serviceStatus = await location.requestService();
print('status -> $serviceStatus');
}
return serviceStatus;
}
When its calling await location.requestService(), it is showing the dialog to enable GPS but after that it never returns the result.
Even its not executing the print() function.
What am i doing wrong here?
Any help would be very appreciated! Thanks in advance.
I had the same issue. It could be solved by upgrading your Flutter project, follow this link https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects
Try this code to check wather permission enabled, service enabled than it returns true else false. Must configure "location" package related configuration in android and ios projects.
Future<bool> checkServiceStatus() async {
final Location location = Location();
final locationPermission = await location.hasPermission();
if (locationPermission == PermissionStatus.granted) {
final locationServiceEnabled = await location.serviceEnabled();
if (locationServiceEnabled == true) {
return true;
} else {
final requestServiceStatus = await location.requestService();
if (requestServiceStatus == true) {
return true;
} else {
BotToast.showSimpleNotification(
title: "Enable GPS to allow this feature");
return false;
}
}
} else {
BotToast.showSimpleNotification(title: "Required location permission to allow this feature");
return false;
}
}

Facebook login flutter app error with native login

I am trying to integrate facebook login with Flutter. Facebook login working with FacebookLoginBehavior.webViewOnly but I want to login with native dialog. This is not woking in flutter. (iOS only)....
Future<bool> facebookLogin(
BuildContext context, bool isCoach, AuthMode authMode) async {
final facebookLogin = FacebookLogin();
facebookLogin.loginBehavior = FacebookLoginBehavior.nativeOnly;
final result = await facebookLogin.logInWithReadPermissions(['email']);
print(result.status);
if (result.status == FacebookLoginStatus.loggedIn) {
var _token = result.accessToken.token;
return true;
}
return false;
}
Logs: flutter: FacebookLoginStatus.cancelledByUser
As per the issue list for the plugin this is a bug for iOS 13 (https://github.com/roughike/flutter_facebook_login/issues/195)
Use the device_info package and you can put the following check for the iOS 13 device so the rest of the world enjoys the native view
if (Platform.isIOS){
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
String iosSystemVersion = iosInfo.systemVersion;
if (iosSystemVersion.startsWith('13')){
print('Running on IOS version $iosSystemVersion. Forcing facebook login to be webViewOnly');
_facebookSignIn.loginBehavior = FacebookLoginBehavior.webViewOnly;
}
}

Device Country in Flutter

I am trying to get country of device (Android) in Flutter. I used this tutorial, but I think it is the wrong approach for my problem.
Locale myLocale = Localizations.localeOf(context);
print(myLocale.languageCode.toString() + ' ' + myLocale.countryCode.toString());
Based on this, I have couple of questions/issues:
I am always getting en US even though I have set device language to Urdu - Pakistan. So what am I missing?
I want to display certain app items based on the country the device is in, not based on language as people living in Pakistan with language set to English (US) will get items actually intended for USA based users. So, should I use geoLocation and get longitude and latitude and decide based on that data? Or is there any other simpler approach just to get country of user?
Thanking in anticipation.
Add this 2 library in pubspec.yaml
geolocator: ^5.1.1
geocoder: ^0.2.1
Then Add permission for Location access. Check here
At last call this method where you want.
Future<String> getCountryName() async {
Position position = await Geolocator().getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
debugPrint('location: ${position.latitude}');
final coordinates = new Coordinates(position.latitude, position.longitude);
var addresses = await Geocoder.local.findAddressesFromCoordinates(coordinates);
var first = addresses.first;
return first.countryName; // this will return country name
}
if you are looking to get the device country without using "geolocator" so you don't need to get the device permission, you can use the below code:
Future<String> getCountry() async{
Network n = new Network("http://ip-api.com/json");
locationSTR = (await n.getData());
locationx = jsonDecode(locationSTR);
return locationx["country"];
}
The Network class code is below:
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
class Network {
final String url;
Network(this.url);
Future<String> apiRequest(Map jsonMap) async {
HttpClient httpClient = new HttpClient();
HttpClientRequest request = await httpClient.postUrl(Uri.parse(url));
request.headers.set('content-type', 'application/x-www-form-urlencoded');
request.add(utf8.encode(json.encode(jsonMap)));
HttpClientResponse response = await request.close();
// todo - you should check the response.statusCode
String reply = await response.transform(utf8.decoder).join();
httpClient.close();
return reply;
}
Future<String> sendData(Map data) async {
http.Response response = await http.post(url,
headers: {'Content-Type': 'application/json; charset=UTF-8'},
body: jsonEncode(data));
if (response.statusCode == 200)
return (response.body);
else
return 'No Data';
}
Future<String> getData() async {
http.Response response = await http.post(url,
headers: {'Content-Type': 'application/x-www-form-urlencoded'});
if (response.statusCode == 200)
return (response.body);
else
return 'No Data';
}
}
you can get the city and countrycode and internet provider as well.
try this
import 'dart:io' show Platform;
String localeName = Platform.localeName;
Use the flutter_sim_country_code, with this package you can get a user localizations variables and country code directly from the user sim and network provider. For example;
networkCountryIso,
simCountryIso,
these variable always holds the country code,
please use package https://pub.dev/packages/devicelocale
I have tested with real device, it works fine
code snippet
String locale = await Devicelocale.currentLocale;
full code
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:devicelocale/devicelocale.dart';
void main() => runApp(MyApp());
/// Demo getting a device locale
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List _languages = List();
String _locale;
#override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
List languages;
String currentLocale;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
languages = await Devicelocale.preferredLanguages;
print(languages);
} on PlatformException {
print("Error obtaining preferred languages");
}
try {
currentLocale = await Devicelocale.currentLocale;
print(currentLocale);
} on PlatformException {
print("Error obtaining current locale");
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_languages = languages;
_locale = currentLocale;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(
children: <Widget>[
Text("Current locale: "),
Text('$_locale'),
Text("Preferred Languages: "),
Text(_languages.toString()),
],
)
),
),
);
}
}
I had the same problem working on an app with country specific functionality. All these methods mentioned here are good, having gone through them all myself I realised none will cover all possible scenarios.
So, I changed the approach to the problem. Instead of making decision programmatically, which country user is in and going ahead with it. I thought it is better to identify the user's country using one of the methods that suits your app most and then present it to the user. If they wish they can change it, otherwise in most cases auto detection is valid.
For my app I presented users with country when logging in and a flat button at bottom to change it if it is not correct.
This solution requires location service to be enabled.
Add these two dependencies in your pubspec.yaml file
geolocator: ^7.0.3
geocoding: ^2.0.0
Future<String> getCountryCodeName() async {
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
List<Placemark> address =
await placemarkFromCoordinates(position.latitude, position.longitude);
Placemark placeMark = address.first;
String country = placeMark.country;
return country; // this will return country }
You have to use a geolocator and geocode plugin. then create this method use below code. In Android Manifest.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
Function
Future<String> getCountryName() async {
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
await Geolocator.requestPermission();
}
if (permission == LocationPermission.deniedForever) {
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.');
}
}
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
debugPrint('location: ${position.latitude}');
final coordinates = new Coordinates(position.latitude, position.longitude);
var addresses =
await Geocoder.local.findAddressesFromCoordinates(coordinates);
var first = addresses.first;
print(first.countryName);
return first.countryName;
}
Try this. Don't forget to provide necessary permissions in android manifest and ios as well.
import 'package:geolocator/geolocator.dart';
final Geolocator geolocator = Geolocator()..forceAndroidLocationManager;
geolocator
.getCurrentPosition(desiredAccuracy: LocationAccuracy.best)
.then((Position position) {
setState(() {
_currentPosition = position;
});
}).catchError((e) {
print(e);
});
Building on #chunhunghan's answer, you can use the plugin https://pub.dev/packages/devicelocale. To get the country code, you have to use:
Locale l = await Devicelocale.currentAsLocale;
String countryCode = l.countryCode;
For the full code, check: https://pub.dev/packages/devicelocale#-example-tab-
var code = WidgetsBinding.instance.window.locale.countryCode;

How to save to web local storage in flutter web

I have a web site built with flutter for web and currently, am trying to save to web local storage or cookie but can't seem to find any plugin or way to archive that.
You can use window.localStorage from dart:html
import 'dart:html';
class IdRepository {
final Storage _localStorage = window.localStorage;
Future save(String id) async {
_localStorage['selected_id'] = id;
}
Future<String> getId() async => _localStorage['selected_id'];
Future invalidate() async {
_localStorage.remove('selected_id');
}
}
shared_preferences dart package now supports local storage for the web from version 0.5.4.7+
Similar to shared preference on Android and iOS, the following is the code snippet for local storage on web
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart'; // rememeber to import shared_preferences: ^0.5.4+8
void main() {
runApp(MaterialApp(
home: Scaffold(
body: Center(
child: RaisedButton(
onPressed: _incrementCounter,
child: Text('Increment Counter'),
),
),
),
));
}
_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int counter = (prefs.getInt('counter') ?? 0) + 1;
print('Pressed $counter times.');
await prefs.setInt('counter', counter);
}
I ran into a similar issue where my preferences weren't being persisted across runs. I thought window.localStorage was broken. I discovered that Flutter was simply launching with a new port number every time by default, so window.localStorage was getting wiped out.
This ticket talks about setting an explicit port. This fixed my issue, and now window.localStorage persists across runs:
https://github.com/Dart-Code/Dart-Code/issues/1769
In VS Code, you can set the port number in your launch.json file:
{
"name": "Flutter",
"request": "launch",
"type": "dart",
"args": ["--web-port", "8686"]
},
With flutter 1.10 we can use universal_html package:
import 'package:universal_html/html.dart';
// ...
// read preference
var myPref = window.localStorage['mypref'];
// ...
// write preference
window.localStorage['mypref'] = myPref;
I am using shared_preferences package to store data on local storage
class SessionManager {
static SessionManager manager;
static SharedPreferences _prefs;
static Future<SessionManager> getInstance() async {
if (manager == null || _prefs == null) {
manager = SessionManager();
_prefs = await SharedPreferences.getInstance();
}
return manager;
}
void putCityId(String cityId) {
_prefs.setString("KEY_CITY_ID", cityId);
}
String getCityId() {
return _prefs.getString("KEY_CITY_ID") ?? "";
}
}
shared_preferences store data for the current session only.
If you want to store data permanently then you should use cookie to store data.
import 'dart:html';
class CookieManager {
static CookieManager _manager;
static getInstance() {
if (_manager == null) {
_manager = CookieManager();
}
return _manager;
}
void _addToCookie(String key, String value) {
// 2592000 sec = 30 days.
document.cookie = "$key=$value; max-age=2592000; path=/;";
}
String _getCookie(String key) {
String cookies = document.cookie;
List<String> listValues = cookies.isNotEmpty ? cookies.split(";") : List();
String matchVal = "";
for (int i = 0; i < listValues.length; i++) {
List<String> map = listValues[i].split("=");
String _key = map[0].trim();
String _val = map[1].trim();
if (key == _key) {
matchVal = _val;
break;
}
}
return matchVal;
}
}
After upgrading to flutter 1.9, 'dart:html' is not compiled anymore as it is not part of dart SDK that shipped with Flutter.
We can use this package at the moment as it support Android, IOS and WEB:
crypted_preferences