Sometimes Provider doesn't trigger after anonymous sign-up in flutter - android

Hello fellow flutter developers,
I have a bug that's been making my life pretty complicated while developing my own app using Flutter. It goes like this:
User opens app
if they're not signed in, they're redirected to USP page.
If they click next, they're redirected to the sign up page.
Sign-up is provided by Firebase and it's anonymous
If sign-up is successful, a Provider should be triggered and a new page is loaded
The bug is that sometimes the user is sent back to the USP page (meaning their user_id is null) despite no exception between thrown during sign-up. If I force the navigation to the signed-in page, then the user doesn't have an user_id and that's an issue for me.
Any one experienced and fixed the same issue? Below you can see how I built my code, maybe this can help?
This is my main file:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await SignIn.initializeFirebase();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
getSignInUser() {
return SignIn().user;
}
Widget getMaterialApp() {
return MaterialApp(
title: 'app_title',
home: HomePagePicker(),
onGenerateRoute: RouteGenerator.generateRoute,
debugShowCheckedModeBanner: false,
);
}
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<MyUser?>.value(value: SignIn().user, initialData: null),
],
child: getMaterialApp(),
);
}
}
class HomePagePicker extends StatefulWidget {
#override
_HomePagePickerState createState() => _HomePagePickerState();
}
class _HomePagePickerState extends State<HomePagePicker> {
#override
Widget build(BuildContext context) {
MyUser? myUser = Provider.of<MyUser?>(context);
if (myUser == null) return IntroScreen(); // this shows the USPs
else {
// this takes you to the signed-in part of the app
return AnotherScreen();
}
}
}
The IntroScreen is a very simple screen with a few USPs and a button to open the registration page. It goes something like
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'login.dart';
class IntroScreen extends StatelessWidget {
static const routeName = '/introScreen';
#override
Widget build(BuildContext context) {
analytics.setScreenName("introScreen");
return Scaffold(
body: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(statusBarColor: ThemeConfig.darkPrimary),
child: Column(...), // show the USPs
floatingActionButton: getFloatingButton(context)
);
}
Widget getFloatingButton(BuildContext buildContext) {
return FloatingActionButton(
backgroundColor: ThemeConfig.primary,
foregroundColor: Colors.white,
child: Icon(Icons.arrow_forward),
onPressed: () {
navigateToScreen(MyLogin.routeName, buildContext, null);
},
);
}
// this is in another file normally but putting it here for completeness
navigateToScreen(String routeName, BuildContext context, Object? arguments) {
Navigator.pushNamed(
context,
routeName,
arguments: arguments
);
}
The important bit in the registration page is this
Future<void> finalizeRegistration(String userName, String userToken) async {
await usersCollection.add({'userName': userName, "userToken": userToken});
}
Future<void> registerUser(String userName) {
return SignIn()
.anonymousSignIn(userName)
.timeout(Duration(seconds: 2))
.then((userToken) {
finalizeRegistration(userName, userToken)
.then((value) => Navigator.pop(context));
})
.catchError((error) {
registrationErrorDialog();
});
}
The SignIn class is the following
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:house_party/models/MyUser.dart';
class SignIn {
final FirebaseAuth _auth = FirebaseAuth.instance;
static Future<FirebaseApp> initializeFirebase() async {
FirebaseApp firebaseApp = await Firebase.initializeApp();
return firebaseApp;
}
Stream<MyUser?> get user {
return _auth
.authStateChanges()
.asyncMap(getUser);
}
Future<String> anonymousSignIn(String userName) async {
var authResult = await _auth.signInAnonymously();
return authResult.user!.uid;
}
Future<MyUser?> getUser(User? user) async {
if (user == null) {
return Future.value(null);
}
return FirebaseFirestore.instance
.collection('users')
.where('userToken', isEqualTo: user.uid)
.get()
.then((res) {
if (res.docs.isNotEmpty) {
return MyUser.fromFireStore(res.docs.first.data());
} else {
return null;
}
});
}
}
Finally, I'm using these versions of firebase
firebase_core: ^1.0.0
cloud_firestore: ^1.0.0
firebase_dynamic_links: ^2.0.0
firebase_auth: 1.1.2
firebase_analytics: ^8.1.1
I hope the problem statement is clear enough!
Thanks in advance!

I fixed this problem (or at least I'm not able to reproduce it anymore) by upgrading the firebase libraries as follows
firebase_core: ^1.4.0
cloud_firestore: ^2.4.0
firebase_dynamic_links: ^2.0.7
firebase_auth: 3.0.1
firebase_analytics: ^8.2.0

Related

fix net::err_unknown_url_scheme whatsapp link on flutter webview

Please I want to know how to launch WhatsApp in the Flutter webview app or launch WhatsApp from the browser in Flutter, have used many codes with no errors but they do not work.Am using mac m1
and vscode
import 'package:coinpaga/Connectivity_Provider.dart';
import 'package:coinpaga/services/local_notification_service.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'homepage.dart';
import 'package:hexcolor/hexcolor.dart';
import 'package:colorful_safe_area/colorful_safe_area.dart';
/// Receive message when app is in background solution for on
message
Future<void> backgroundHandler(RemoteMessage message)async{
print(message.data.toString());
print(message.notification!.title);
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
LocalNotificationServices.initialize();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(backgroundHandler);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => ConnectivityProvider(),
child: HomePage(),
)
],
child:MaterialApp(
title: 'coinpaga',
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: ColorfulSafeArea(
color: HexColor("#2e2a42"),
top: true,
bottom: false,
left: false,
right: false,
child: HomePage(),
)
),
);
}
}
Home.dart
import 'package:coinpaga/Connectivity_Provider.dart';
import 'package:coinpaga/no_internet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
import 'package:provider/provider.dart';
class HomePage extends StatefulWidget {
// ignore: unused_field
final _flutterwebview = FlutterWebviewPlugin();
HomePage({ Key? key }) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
void initState() {
super.initState();
Provider.of<ConnectivityProvider>(context, listen:
false).startMonitoring();
}
#override
Widget build(BuildContext context) {
return pageUI();
}
#override
void dispose() {
_flutterwebview.dispose();
super.dispose();
}
}
Widget pageUI() {
return Consumer<ConnectivityProvider>(
builder: (context, model, child) {
return model.isOnline
? WebviewScaffold(
url: 'https://coinpaga.com',
withLocalStorage: true,
withJavascript: true,
scrollBar: false,
initialChild: Center(child: Text('Loading...')),
) : NoInternet();
},
);
}
// ignore: camel_case_types
class _flutterwebview {
static void dispose() {}
}
Please help me go through it.
String text = "Hello World !! Hey There";
String url = "https://wa.me/?text=${Uri.encodeFull(text)}";
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url),
mode: LaunchMode.externalApplication);
}
First, add url_launcher, then I use this code to launch whatsapp on flutter webview and works.
WebView(
initialUrl: getUrl(_url),
javascriptMode: JavascriptMode.unrestricted,
navigationDelegate: (NavigationRequest request) async {
if (request.url
.startsWith('https://api.whatsapp.com/send?phone')) {
print('blocking navigation to $request}');
List<String> urlSplitted = request.url.split("&text=");
String phone = "0123456789";
String message =
urlSplitted.last.toString().replaceAll("%20", " ");
await _launchURL(
"https://wa.me/$phone/?text=${Uri.parse(message)}");
return NavigationDecision.prevent;
}
print('allowing navigation to $request');
return NavigationDecision.navigate;
},
)
_launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
You can use url_launcher to launch URLs.
You can give https://api.whatsapp.com/send/?phone=(phone_number) URL to launch.
For the launching the WhatsApp Website use launch('https://api.whatsapp.com/send/?phone=(phone_number)')
Make sure you give your country code without (+).

FirebaseAuth.instance.currentUser!.then; then isn't defined

So i want to implement role based authentication to my app in flutter. Only users with the permissions should get to this site. Im getting the error:
The method 'then' isn't defined for the type 'User'.
Try correcting the name to the name of an existing method, or defining a method named 'then'.
I've tried to solve it for 2 hours now, but every tutorial is outdated or not for my usecase.
Full code of Authentication Handler:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:learnon/screens/lehrerstartpage.dart';
import '../screens/startscreen.dart';
import '../screens/auth_screen.dart';
class AutoLoginHandler extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
//Streambuilder looks if data is avaliable
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data != null) {
return MainPage(); //when data here goto Startscreen
}
return LoginScreen(); //when no data is here goto Login
},
);
}
final FirebaseAuth auth = FirebaseAuth.instance;
authorizeAccess(BuildContext context) {
FirebaseAuth.instance.currentUser!.then((user) {
FirebaseFirestore.instance
.collection('/users')
.where('uid', isEqualTo: user.uid)
.get()
.then((results) {
if (results.size > 0) {
if (results.docs[0].data()['role'] == 'admin')
{
Navigator.of(context).push(new
MaterialPageRoute(
builder: (BuildContext context) => new
LehrerMainPage()));
}
}
});
});
}
}

Flutter: local_auth can be bypassed by pressing the back button

I'm using local_auth for user verification.
The root widget of my app is a stateless widget.
The bug:
The authentication screen pops up as usual. If the fingerprint (or pin) matches, the user can then access the app. However, if the back button is pressed (while the authentication screen is still up), the authentication window vanishes and the user can access the app without authenticating.
I'm using Flutter-2.5.3 and local_auth-1.1.8.
This is the main.dart:
//other imports here
import 'package:local_auth/local_auth.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
var localAuth = LocalAuthentication();
SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.getBool('auth') == true) {
await localAuth.authenticate(
localizedReason: 'Authenticate to access Notes',
useErrorDialogs: true,
stickyAuth: true,);
}
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]).then((_) {
runApp(ProviderScope(child: MyApp()));
});
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
//returns my widget
}
}
I tried moving the runApp block under a conditional, such that the main root window gets called only when the authentication was successful. However the result remained the same.
This was what I did:
if (prefs.getBool('auth') == true) {
var authenticate = await localAuth.authenticate(
localizedReason: 'Authenticate to access Notes',
useErrorDialogs: true,
stickyAuth: true,);
if (authenticate == true) {
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]).then((_) {
runApp(ProviderScope(child: MyApp()));
});
}
}
This is what worked for me:
Changing MyApp to a StatefulWidget.
Adding an awaited function that attempts to authenticate the user before the user can access the widget (that is, the build function).
Modifying the code:
//other imports here
import 'package:local_auth/local_auth.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
//removing the authentication block from the main method
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]).then((_) {
runApp(ProviderScope(child: MyApp()));
});
}
class MyApp extends StatefulWidget { //changing MyApp to StatefulWidget
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
_authenticate(); //the function that handles authentication
}
void _authenticate() async {
var localAuth = LocalAuthentication();
SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.getBool('auth') == true) {
var authenticate = await localAuth.authenticate(
localizedReason: 'Authenticate to access Notes',
useErrorDialogs: true,
//Not using stickyAuth because: https://github.com/flutter/flutter/issues/83773
// stickyAuth: true,
);
if (authenticate != true)
exit(0); //exiting the app if the authentication failed
}
}
#override
Widget build(BuildContext context) {
//returns my widget
}
}

Flutter Plaid package showing blank screen on iOS

Good day,
I have a flutter app which I have integrated a Plaid flutter package, it works well on android but shows a white screen on iOS.
I have added
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
</dict>
in the Info.plist file, but this doesn't seem to make it work.
Below are screenshots
Please I need help on what to do to make iOS platform work.
Here is my configuration
Configuration configuration = Configuration(
plaidPublicKey: '$PLAID_PUBLIC_KEY',
plaidBaseUrl: 'https://cdn.plaid.com/link/v2/stable/link.html',
plaidEnvironment: '$PLAID_ENV',
environmentPlaidPathAccessToken:
'https://sandbox.plaid.com/item/public_token/exchange',
environmentPlaidPathStripeToken:
'https://sandbox.plaid.com/processor/stripe/bank_account_token/create',
// plaidClientId: 'yourPlaidClientId',
// secret: plaidSandbox ? 'yourSecret' : '',
clientName: '$PLAID_CLIENT_NAME',
// webhook: 'Webhook Url',
products: 'auth, transactions',
selectAccount: 'true',
plaidClientId: null);
FlutterPlaidApi flutterPlaidApi = FlutterPlaidApi(configuration);
WidgetsBinding.instance.addPostFrameCallback((_) {
// Add Your Code here.
});
flutterPlaidApi.launch(context, (Result result) async {
// show loader screen when returning back to the app
showLoadingScreen(context, message: 'Processing...');
// send the data to the api
var response = await BankService().linkUserAccountWithSila(
accountName: result.accountName,
publicToken: result.token,
email: 'email#example.com');
final responseJson = json.decode(response.body);
if (response.statusCode >= 200 && response.statusCode <= 299) {
var token = await getToken();
var client = new http.Client();
List<String> urls = [
'getDefaultAccount',
'all',
];
try {
List<http.Response> list =
await Future.wait(urls.map((urlId) => client.get(
'$kBaseUrl/account/$urlId',
headers: {HttpHeaders.authorizationHeader: "Bearer $token"},
)));
if (list[0].statusCode == 200 && list[1].statusCode == 200) {
var defaultAccount = jsonDecode(list[0].body);
var plaidAccounts = jsonDecode(list[1].body);
Provider.of<TransferProvider>(context, listen: false)
.updatePlaidBankAccounts(
plaidAccount:
plaidAccounts['data'] != null ? plaidAccounts['data'] : [],
account: defaultAccount['data'],
);
}
} catch (e) {} finally {
client.close();
}
Navigator.pop(context);
Toast.show('Account linked successfully', context,
duration: Toast.LENGTH_LONG, gravity: Toast.CENTER);
} else {
Toast.show('Something went wrong, please try again later', context,
duration: Toast.LENGTH_LONG, gravity: Toast.CENTER);
// error
}
}, stripeToken: false);
}
Try this code: https://github.com/flutter/flutter/issues/49483
Diclaimer: this is not my code. I am copying it here so that if the original post gets deleted, the source code is still available here. All credits to the original author.
Steps to Reproduce
Register for a free sandbox testing account at Plaid (running the webview in sandbox requires a public_key)
Create new project and add plaid_screen.dart to lib
In plaid_screen.dart assign the public key from Plaid into the queryParameters "key" key
Replace default main.dart content with the setup below
add webview_flutter: ^0.3.19+5 to pubspec.yaml
add <key>io.flutter.embedded_views_preview</key><true/> to ios/Runner/info.plist
Run main.dart
main.dart:
import 'package:bug/plaid_screen.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: PlaidScreen.id,
routes: {
PlaidScreen.id: (context) => PlaidScreen(),
},
);
}
}
plaid_screen.dart:
(Note: public key must be obtained and pasted below)
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
String authority = 'cdn.plaid.com';
String unencodedPath = '/link/v2/stable/link.html';
Map<String, String> queryParameters = {
"key": "{{PASTE_PUBLIC_KEY}}",
"product": "auth",
"apiVersion": "v2", // set this to "v1" if using the legacy Plaid API
"env": "sandbox",
"clientName": "Test App",
"selectAccount": "true",
};
// documentation: https://plaid.com/docs/#webview-integration
class PlaidScreen extends StatefulWidget {
static const id = 'plaid_screen_id';
#override
_PlaidScreenState createState() => _PlaidScreenState();
}
class _PlaidScreenState extends State<PlaidScreen> {
Uri uri = Uri.https(authority, unencodedPath, queryParameters);
Completer<WebViewController> _controller = Completer<WebViewController>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: WebView(
javascriptMode: JavascriptMode.unrestricted,
initialUrl: uri.toString(),
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
navigationDelegate: (NavigationRequest navRequest) {
debugPrint("NavigationRequest URL: ${navRequest.url}");
if (navRequest.url.contains('plaidlink://')) {
return NavigationDecision.prevent;
}
debugPrint(navRequest.url.toString());
return NavigationDecision.navigate;
},
),
),
);
}
}

Flutter Auto Login using local database

I'm new to flutter and trying to create a login app.
I have 2 screens.
Login (If user enters correct credentials, store user information to local db(sqflite) and navigate to home).
Home (have logout option).
I'm trying to achieve auto login i.e when user closes the app without logging out, the app should navigate to home automatically without logging again when app reopens.
My logic:
If user enters valid credentials, clear the db table and insert newly entered credentials.
Auto login - when app starts, check if record count in db table is 1, then navigate to home else login.
Here's the code which I have tried:
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final dbHelper = DatabaseHelper.instance;
bool logged = false;
#override
void initState() {
super.initState();
autoLogIn();
}
void autoLogIn() async {
if (await dbHelper.queryRowCount() == 1) {
setState(() {
logged = true;
});
return;
}
}
#override
Widget build(BuildContext context) {
return logged ? HomeScreen(): LoginScreen();
}
}
It makes me as if, the widget is build before the state of logged is changed.
How can I achieve auto login assuming there is no issue with database(sqflite) implementation.
I have used SharedPreferences instead of local database and that worked.
Below is the minimal implementation
import 'package:IsBuddy/Screens/Dashboard/dashboard.dart';
import 'package:IsBuddy/Screens/Login/login_screen.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AutoLogin extends StatefulWidget {
#override
_AutoLoginState createState() => _AutoLoginState();
}
class _AutoLoginState extends State<AutoLogin> {
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Future<bool> logged;
#override
void initState() {
logged = _prefs.then((SharedPreferences prefs) {
return (prefs.getBool('logged') ?? false);
});
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
future: logged,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Container(
child: Center(
child: CircularProgressIndicator(),
));
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
return snapshot.data ? Dashboard() : LoginScreen();
}
}
},
);
}
}

Categories

Resources