Error signing in using FirebaseAuth in a Flutter app using a physical iOS device - ios

I have implemented a simple application using Flutter and FirebaseAuth where I want a user to sign in giving an email and a password, this application works as intended in the iOS simulators however, when I try side loading the application on to a physical iOS device I get several errors and the signing in process fails and the app doesn't continue there onwards. I've shown the code, the errors that arises and I have listed the steps that I've taken so far to mitigate this of which none has worked.
Code
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'screens/other/LoadingScreen.dart';
import 'screens/other/ErrorScreen.dart';
import 'screens/other/SignupScreen.dart';
import 'screens/other/HomeScreen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
// This widget is the root of your application.
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<CovidHound> {
bool _initialized = false;
bool _error = false;
String _email = "";
String _password = "";
void initializeFlutterFire() async {
try {
await Firebase.initializeApp();
print("Init firebase");
setState(() {
_initialized = true;
});
} catch (e) {
print("Error init firebase:${e}");
setState(() {
_error = true;
});
}
}
Future<void> onTapSignIn() async {
try {
await FirebaseAuth.instance
.signInWithEmailAndPassword(email: _email, password: _password);
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
print('No user found for that email.');
} else if (e.code == 'wrong-password') {
print('Wrong password provided for that user.');
}
} catch (e) {
print("Error signing in: $e");
}
if (FirebaseAuth.instance.currentUser != null) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomeScreen(),
fullscreenDialog: true,
),
);
}
}
#override
void initState() {
super.initState();
initializeFlutterFire();
}
#override
Widget build(BuildContext context) {
if(_error) {
return ErrorScreen();
}
if (!_initialized) {
return LoadingScreen();
}
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
children: [
TextField(
decoration: InputDecoration(hintText: "Email"),
onChanged: (value) {
_email = value;
},
),
TextField(
decoration: InputDecoration(hintText: "Password"),
onChanged: (value) {
_password = value;
},
),
TextButton(
onPressed: () {
onTapSignIn();
},
child: Text("Sign In"),
),
],
),
),
),
);
}
}
Errors
So far I have tried the following,
Properly configuring Firebase according to the documentation.
Cleaning Xcode workspace and builds using flutter clean.
Updating iOS and Xcode to latest versions.
Upgrading Flutter.
Adding permissions for Privacy - Local Network Usage Description in the info.plist as demonstrated in ( https://flutter.dev/docs/development/add-to-app/ios/project-setup#local-network-privacy-permissions )

Currently, you do not await your initializeFlutterFire() function, which could lead to your error message, because the subsequent code is executed before the initializing of Firebase.
Move your initializeFlutterFire() outside the MyApp or it's State class, then try to change the return type to Future<void>, then call this function in main() (instead of in initState()) for example:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeFlutterFire();
runApp(MyApp());
}
Firebase (FlutterFire) requires you to initialise the plugin before you start your App's instance to avoid errors like this.

Related

StateError (Bad state: No element) on IOS only

This error does not occur on Android or web but only on IOS. It seem very trivial but I can't figure out what's wrong.
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'dart:convert';
import 'package:qr_code_scanner/qr_code_scanner.dart';
class ScanQrPage extends StatefulWidget {
#override
_ScanQrPageState createState() => _ScanQrPageState();
}
class _ScanQrPageState extends State<ScanQrPage> {
final qrKey = GlobalKey();
late QRViewController qrViewController;
late Barcode barcode;
// In order to get hot reload to work we need to pause the camera if the platform
// is android, or resume the camera if the platform is iOS.
#override
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
qrViewController.pauseCamera();
} else if (Platform.isIOS) {
qrViewController.resumeCamera();
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
Navigator.of(context).pop("");
return new Future(() => true);
},
child: Scaffold(
body: Stack(
children: [
buildQrView(context),
],
),
),
);
}
Widget buildQrView(BuildContext context) {
return QRView(
onQRViewCreated: onQRViewCreated,
key: qrKey,
overlay: QrScannerOverlayShape(
cutOutSize: MediaQuery.of(context).size.width * 0.8),
onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p),
);
}
void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) {
log('${DateTime.now().toIso8601String()}_onPermissionSet $p');
if (!p) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('no Permission')),
);
}
}
#override
void dispose() {
qrViewController.dispose();
super.dispose();
}
void onQRViewCreated(QRViewController qrViewController) {
setState(() {
this.qrViewController = qrViewController;
});
qrViewController.scannedDataStream.listen((event) {
setState(() {
this.barcode = event;
if (Platform.isAndroid) {
qrViewController.pauseCamera();
} else if (Platform.isIOS) {
qrViewController.resumeCamera();
}
String rawData = event.code;
Uri data = Uri.dataFromString(rawData);
String para1 = data.queryParameters["buy"] ??
""; //get parameter with attribute "para1"
Codec<String, String> stringToBase64 = utf8.fuse(base64);
if (para1 != "") {
placer = stringToBase64.decode(para1);
}
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
Navigator.pop(context, placer);
});
});
});
}
}
I've tried all the solutions with the same error found on stackoverflow (addPostFrameCallback and Future(Duration.zero)) but none of them are exactly the same and does not seem to fix my problem.
I don't think I have having the same issue as any of the other questions.
The exception is happening on the Navigator.pop(context, placer);
Does anyone have any idea how to overcome this?
Why does this only happen on IOS?

Remove debug banner from this specific main.dart

Not sure where to add the: debugShowCheckedModeBanner: false, I'm trying to build for ios with xcode.
Here is the current main.dart:
Not sure what I need to change in order to get this to build. I know it has something to do with MaterialApp but I can't figure out the placement.
`import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:kittscoaching/src/app.dart';
import 'package:kittscoaching/src/resources/theme.dart';
void main() {
runApp(App());
}
class App extends StatefulWidget {
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
// Set default `_initialized` and `_error` state to false
bool _initialized = false;
bool _error = false;
// Define an async function to initialize FlutterFire
void initializeFlutterFire() async {
try {
// Wait for Firebase to initialize and set `_initialized` state to true
await Firebase.initializeApp();
setState(() {
_initialized = true;
});
} catch(e) {
// Set `_error` state to true if Firebase initialization fails
setState(() {
_error = true;
});
}
}
#override
void initState() {
initializeFlutterFire();
super.initState();
}
#override
Widget build(BuildContext context) {
// Show error message if initialization failed
if(_error) {
//TODO:
//return SomethingWentWrong();
}
// Show a loader until FlutterFire is initialized
if (!_initialized) {
// TODO:
return Container(
decoration: BoxDecoration(color: KittsTheme.primary),
child: Center(
child: Directionality(
textDirection: TextDirection.ltr,
child: Text('Loading...')
)
)
);
}
return MyApp();
}
}`
There isn't a MaterialApp in the code that you're showing. Find the MaterialApp, if there is one, and apply the property there.
Or run your app in release mode: flutter run --release
Or open dev tools and click off the debug banner from there.
Hope this will help you
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:kittscoaching/src/app.dart';
import 'package:kittscoaching/src/resources/theme.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: App(),
debugShowCheckedModeBanner: false,
);
}
}
class App extends StatefulWidget {
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
// Set default `_initialized` and `_error` state to false
bool _initialized = false;
bool _error = false;
// Define an async function to initialize FlutterFire
void initializeFlutterFire() async {
try {
await Firebase.initializeApp();
setState(() {
_initialized = true;
});
} catch(e) {
// Set `_error` state to true if Firebase initialization fails
setState(() {
_error = true;
});
}
}
#override
void initState() {
initializeFlutterFire();
super.initState();
}
#override
Widget build(BuildContext context) {
// Show error message if initialization failed
if(_error) {
//TODO:
//return SomethingWentWrong();
}
// Show a loader until FlutterFire is initialized
if (!_initialized) {
// TODO:
return Container(
decoration: BoxDecoration(color: KittsTheme.primary),
child: Center(
child: Directionality(
textDirection: TextDirection.ltr,
child: Text('Loading...')
)
)
);
}
return MyApp();
}
}
Simply you have to specify it in MaterialApp widget. Find the sample code below.
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SafeArea(
),
),

Run Flutter App only if internet connection is available

I want my flutter app run only if internet connection is available.
If the internet is not present show a dialog(internet is not present)
I'm using conectivity plugin but still not satisfied.
Here is my main function
Future main() async {
try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
print('connected');
}
} on SocketException catch (_) {
print('not connected');
}
runApp(MyApp());}
You can't use dialog in main() method directly because there is no valid context available yet.
Here is the basic code of what you are looking for.
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
Timer.run(() {
try {
InternetAddress.lookup('google.com').then((result) {
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
print('connected');
} else {
_showDialog(); // show dialog
}
}).catchError((error) {
_showDialog(); // show dialog
});
} on SocketException catch (_) {
_showDialog();
print('not connected'); // show dialog
}
});
}
void _showDialog() {
// dialog implementation
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("Internet needed!"),
content: Text("You may want to exit the app here"),
actions: <Widget>[FlatButton(child: Text("EXIT"), onPressed: () {})],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Internet")),
body: Center(
child: Text("Working ..."),
),
);
}
}

Open the youtube app from flutter app on iOS

I basically want to open a specific youtube video from my app, when a button is pressed. If the youtube app is installed on the user's device, then the video should be opened in the youtube app (and not in the browser or a separate webview).
I used the url_launcher package for that, and it works fine on android. However on iOS the youtube app is not opened even if it is installed, instead a separate web window is opened, where the corresponding youtube url is shown as a webpage.
I thought, that I could override this behaviour like so:
_launchURL() async {
if (Platform.isIOS) {
if (await canLaunch('youtube://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw')) {
await launch('youtube://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw');
} else {
if (await canLaunch('https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw')) {
await launch('https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw');
} else {
throw 'Could not launch https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw';
}
}
} else {
const url = 'https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw';
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
}
but it didn’t work. In case you wonder, I use the following imports:
import 'dart:io' show Platform;
import 'package:url_launcher/url_launcher.dart';
I am pretty sure, the youtube:// URL-Scheme works (launches the YouTube app), because I tested it on third party apps (Launch Center Pro and Pythonista).
The last thing I was not able to test, is if the Platform.isIOS is really true on my IPhone.
Is there a working way, to open the YouTube App from flutter?
I fixed it. I had to set forceSafariVC: false, because it is true on default, which causes the url to be opened inside a sort of webview inside the app.
_launchURL() async {
if (Platform.isIOS) {
if (await canLaunch('youtube://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw')) {
await launch('youtube://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw', forceSafariVC: false);
} else {
if (await canLaunch('https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw')) {
await launch('https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw');
} else {
throw 'Could not launch https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw';
}
}
} else {
const url = 'https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw';
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
This is actually documented in the url_launcher docs, but somewhat hidden...
You don't have to have all that if/else clauses. The most important thing to take into consideration is that whether the device has the YouTube app or not, regardless of the O.S (and remember to define your function as Future because of the async):
Future<void> _launchYoutubeVideo(String _youtubeUrl) async {
if (_youtubeUrl != null && _youtubeUrl.isNotEmpty) {
if (await canLaunch(_youtubeUrl)) {
final bool _nativeAppLaunchSucceeded = await launch(
_youtubeUrl,
forceSafariVC: false,
universalLinksOnly: true,
);
if (!_nativeAppLaunchSucceeded) {
await launch(_youtubeUrl, forceSafariVC: true);
}
}
}
}
The thing to highlight here to avoid several if/else si the attribute universalLinksOnly set to true.
I have solved the issue. You can try the below code:
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
void main() => runApp(const HomePage());
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: MyApp(),
);
}
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Future<void>? _launched;
#override
Widget build(BuildContext context) {
const String toLaunch = 'https://www.youtube.com/watch?v=WcZ8lTRTNM0';
return Scaffold(
appBar: AppBar(title: const Text('Flutter Demo')),
body: Center(
child: ElevatedButton(
onPressed: () => setState(() {
_launched = _launchInBrowser(toLaunch);
}),
child: const Text('Launch in browser'),
),
), );
}
Future<void> _launchInBrowser(String url) async {
if (!await launch(
url,
forceSafariVC: true,
forceWebView: false,
headers: <String, String>{'my_header_key': 'my_header_value'},
)) {
throw 'Could not launch $url';
}
}
}
Using same package:
https://pub.dev/packages/url_launcher
Here is latest working example. Most answers above is outdated or using deprecated package. Default mode is LaunchMode.platformDefault. Change to LaunchMode.externalApplication will open youtube app. Hope this helps
Future<dynamic> openUrl(String url, callback) async {
try {
if (await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication)) {
callback(true);
} else {
toastMessage('#1: Could not launch $url');
callback(false);
}
} catch (e) {
toastMessage('#2: Could not launch $url');
callback(false);
}
}

Flutter set startup page based on Shared Preference

I've been trying without success to load different pages according to my Shared Preference settings.
Based on several posts found in stackoverflow, i end up with the following solution:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:testing/screens/login.dart';
import 'package:testing/screens/home.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Widget page = Login();
Future getSharedPrefs() async {
String user = Preferences.local.getString('user');
if (user != null) {
print(user);
this.page = Home();
}
}
#override
void initState() {
super.initState();
this.getSharedPrefs();
}
#override
Widget build(BuildContext context) {
return MaterialApp(home: this.page);
}
}
class Preferences {
static SharedPreferences local;
/// Initializes the Shared Preferences and sets the info towards a global variable
static Future init() async {
local = await SharedPreferences.getInstance();
}
}
The variable user is not null because the print(user) returns a value as expected, but the login screen is always being opened.
Your problem is that your build method returns before your getSharedPrefs future is complete. The getSharedPrefs returns instantly as soon as it's called because it's async and you're treating it as a "Fire and Forget" by not awaiting. Seeing that you can't await in your initState function that makes sense.
This is where you want to use the FutureBuilder widget. Create a Future that returns a boolean (or enum if you want more states) and use a future builder as your home child to return the correct widget.
Create your future
Future<bool> showLoginPage() async {
var sharedPreferences = await SharedPreferences.getInstance();
// sharedPreferences.setString('user', 'hasuser');
String user = sharedPreferences.getString('user');
return user == null;
}
When user is null this will return true. Use this future in a Future builder to listen to the value changes and respond accordingly.
#override
Widget build(BuildContext context) {
return MaterialApp(home: FutureBuilder<bool>(
future: showLoginPage(),
builder: (buildContext, snapshot) {
if(snapshot.hasData) {
if(snapshot.data){
// Return your login here
return Container(color: Colors.blue);
}
// Return your home here
return Container(color: Colors.red);
} else {
// Return loading screen while reading preferences
return Center(child: CircularProgressIndicator());
}
},
));
}
I ran this code and it works fine. You should see a blue screen when login is required and a red screen when there's a user present. Uncomment the line in showLoginPage to test.
There is a much pretty way of doing this.
Assuming that you have some routes and a boolean SharedPreference key called initialized.
You need to use the WidgetsFlutterBinding.ensureInitialized() function before calling runApp() method.
void main() async {
var mapp;
var routes = <String, WidgetBuilder>{
'/initialize': (BuildContext context) => Initialize(),
'/register': (BuildContext context) => Register(),
'/home': (BuildContext context) => Home(),
};
print("Initializing.");
WidgetsFlutterBinding.ensureInitialized();
await SharedPreferencesClass.restore("initialized").then((value) {
if (value) {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Home(),
);
} else {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Initialize(),
);
}
});
print("Done.");
runApp(mapp);
}
The SharedPreference Class Code :
class SharedPreferencesClass {
static Future restore(String key) async {
final SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
return (sharedPrefs.get(key) ?? false);
}
static save(String key, dynamic value) async {
final SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
if (value is bool) {
sharedPrefs.setBool(key, value);
} else if (value is String) {
sharedPrefs.setString(key, value);
} else if (value is int) {
sharedPrefs.setInt(key, value);
} else if (value is double) {
sharedPrefs.setDouble(key, value);
} else if (value is List<String>) {
sharedPrefs.setStringList(key, value);
}
}
}

Resources