Url Launcher using Flutter/Dart - flutter

Good evening folks,
I'm having a slight issue with URL launcher. Basically, what I am trying to do is get a URL from a Future function (retrieved from an API) and then navigate to that URL once a raised button is pressed. The code I have implemented works, but not without a small, pesky bug. The bug appears for about half a second to a second (I'm assuming until the API returns the URL), then the raised button is drawn on to the screen and works fine; I'm then able to navigate to the site. I'm trying to get rid of the bug completely. To save you some time, the relevant FutureBuilder is the second one. Below is my code:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import '../models/yahoo_finance_stock_info.dart';
import '../providers/companies_provider.dart';
class BusinessScreen extends StatefulWidget {
static const routeName = '/business-screen';
#override
_BusinessScreenState createState() => _BusinessScreenState();
}
class _BusinessScreenState extends State<BusinessScreen>
with AutomaticKeepAliveClientMixin<BusinessScreen> {
#override
bool get wantKeepAlive => true;
Future _companyDescription;
Future _urlForUrlLauncher;
var _isInit = true;
#override
void didChangeDependencies() {
if (_isInit) {
final companyTicker = ModalRoute.of(context).settings.arguments as String;
final loadedCompany = Provider.of<Companies>(context, listen: false)
.findByTicker(companyTicker);
_companyDescription =
Companies().getSecurityExtendStats(loadedCompany.tickerSymbol);
_urlForUrlLauncher =
Companies().getHistoricalData(loadedCompany.tickerSymbol);
}
_isInit = false;
super.didChangeDependencies();
}
#override
void initState() {
// TODO: implement initState
super.initState();
_companyDescription;
}
#override
Widget build(BuildContext context) {
super.build(context);
final companyTicker = ModalRoute.of(context).settings.arguments as String;
final loadedCompany = Provider.of<Companies>(context, listen: false)
.findByTicker(companyTicker);
return Container(
color: Colors.black87,
child: Column(
children: <Widget>[
Container(
height: 300,
width: double.infinity,
color: Colors.white,
padding: EdgeInsets.all(10),
child: SingleChildScrollView(
padding: EdgeInsets.only(left: 15),
scrollDirection: Axis.vertical,
child: FutureBuilder<StockInformation>(
future: _companyDescription,
builder: (BuildContext context,
AsyncSnapshot<StockInformation> snapshot) {
if (snapshot.data != null) {
return Text(snapshot.data.assetProfile.longBusinessSummary);
}
return Container(
height: 300,
child: Center(
child: CircularProgressIndicator(),
),
);
},
),
),
),
Container(
height: 75,
width: double.infinity,
child: Center(
child: FutureBuilder(
future: _urlForUrlLauncher,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data[0]['finalLink'] != null) {
String url10K = snapshot.data[0]['finalLink'];
return RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(
color: Colors.grey,
width: 1,
),
),
onPressed: () async {
var url = url10K;
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
},
child: Text(
'Go to company\'s filings',
style: TextStyle(
color: Colors.white,
),
),
);
} else {
return RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(
color: Colors.grey,
width: 1,
),
),
onPressed: null,
child: Text(
'Go to company\'s filings',
style: TextStyle(color: Colors.white,),
),
);
}
},
),
),
)
],
),
);
}
}
Thanks in advance for any and all help with this issue!

In the second FutureBuilder change the condition,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data != null && snapshot.data[0]['finalLink'] != null) {
// raised button with onpressed
} else {
// raised button with onpressed as null
}
}

Related

splash screen before webview loads in flutter

I am trying to build an app that uses webview but before loading it I need an splashscreen that checks if there is internet connection and if there isn't it shows an Snackbar in splashscreen. Do you know how to achieve this?
Use this plugin
https://pub.dev/packages/connectivity_plus
Future<bool> checkInternet() async {
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.mobile) {
return true;
} else if (connectivityResult == ConnectivityResult.wifi) {
return true;
}
return false;
}
Use it like this
if (!(await checkInternet())) {
///show your snackBar here
}
This would help. I achieved this in my personal project.
As you can see am using internet_connection_checker and whenever the internet is down I do a retry which was achieved by calling a restart widget in the main method to safely perform a restart whenever the internet is restored
Splash Page
import 'dart:async';
import 'package:airduka/main.dart';
import 'package:airduka/models/user_model.dart';
import 'package:airduka/widgets/color.dart';
import 'package:airduka/widgets/custom_navigations.dart';
import 'package:airduka/widgets/small_text.dart';
import 'package:flutter/material.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
import 'package:page_transition/page_transition.dart';
class SplashPage extends StatefulWidget {
final String title, description, time;
final UserModel? user;
final List<dynamic>? listMsg;
const SplashPage({Key? key, this.listMsg, required this.title, required this.description, required this.time, this.user}) : super(key: key);
#override
_SplashPageState createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> {
Timer? timer;
Future<InternetConnectionStatus> dataConnection() async{
InternetConnectionStatus statusMain = InternetConnectionStatus.disconnected;
final StreamSubscription<InternetConnectionStatus> listener =
InternetConnectionChecker().onStatusChange.listen(
(InternetConnectionStatus status) {
switch (status) {
case InternetConnectionStatus.connected:
statusMain = InternetConnectionStatus.connected;
break;
case InternetConnectionStatus.disconnected:
statusMain = InternetConnectionStatus.disconnected;
break;
}
},
);
// close listener after 30 seconds, so the program doesn't run forever
await Future<void>.delayed(const Duration(seconds: 1));
await listener.cancel();
return statusMain;
}
#override
void initState() {
timer = Timer(
const Duration(seconds: 5),() => Navigator.pushReplacement(
context,
PageTransition(
child: FutureBuilder<InternetConnectionStatus>(
future: dataConnection(),
builder: (context, AsyncSnapshot snapshot) {
switch(snapshot.connectionState){
case ConnectionState.none:
case ConnectionState.waiting:
return Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: const Color(0xFF00639E),
body: Center(
child: Image.asset(
"assets/animations/jety.gif",
height: 200,
),
),
);
default:
if(snapshot.hasError)
return Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: const Color(0xFF00639E),
body: Center(
child: Image.asset(
"assets/animations/jety.gif",
height: 200,
),
),
);
else if(snapshot.data == InternetConnectionStatus.disconnected)
return Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: const Color(0xFF00639E),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: Image.asset(
"assets/animations/jety.gif",
height: 200,
),
),
const SmallTextWidget(text: "You seem to be offline", color: AppColors.whiteThemeColor,),
const SizedBox(height: 8.0,),
InkWell(
onTap: (){
RestartWidget.restartApp(context);
},
child: Container(
height: 45,
width: 100,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: AppColors.whiteThemeColor
),
child: const Center(child: Text("Retry", style: TextStyle(color: AppColors.blackThemeColor, fontSize: 14.0, fontWeight: FontWeight.bold),)),
),
),
],
),
);
else Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: const Color(0xFF00639E),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: Image.asset(
"assets/animations/jety.gif",
height: 200,
),
),
const SmallTextWidget(text: "Something wrong happened!", color: AppColors.whiteThemeColor,),
const SizedBox(height: 8.0,),
InkWell(
onTap: (){
RestartWidget.restartApp(context); //call restart here
},
child: Container(
height: 45,
width: 100,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: AppColors.whiteThemeColor
),
child: const Center(child: Text("Retry", style: TextStyle(color: AppColors.blackThemeColor, fontSize: 14.0, fontWeight: FontWeight.bold),)),
),
),
],
),
);
return CustomNavigation(msgList: widget.listMsg, user: widget.user,);
}
}),
type: PageTransitionType.fade))
);
super.initState();
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: const Color(0xFF00639E),
body: Center(
child: Image.asset(
"assets/animations/jety.gif",
height: 200,
),
),
);
}
}
Restart Widget in the main method
class RestartWidget extends StatefulWidget {
const RestartWidget({required this.child});
final Widget child;
static void restartApp(BuildContext context) {
context.findAncestorStateOfType<_RestartWidgetState>()!.restartApp();
}
#override
_RestartWidgetState createState() => _RestartWidgetState();
}
class _RestartWidgetState extends State<RestartWidget> {
Key key = UniqueKey();
void restartApp() {
setState(() {
key = UniqueKey();
});
}
#override
Widget build(BuildContext context) {
return KeyedSubtree(
key: key,
child: widget.child,
);
}
}
You can use internet_connection_checker for internet check
bool result = await InternetConnectionChecker().hasConnection;
if(result == true) {
print('YAY! Free cute dog pics!');
} else {
print('No internet :( Reason:');
}
call this code in init method in SplashScreen

Flutter/dart: Parse Server sdk: ParseUser.currentUser() function returning null on reset

I am using "Parse Server sdk" with "back4app" as a backend in my flutter application and I am having trouble calling the current user on initial start up of the app using the function: "ParseUser.currentUser()" but for some reason even after logging in when I restart the application the function returns null
pubspec.yaml
dependencies:
parse_server_sdk: ^2.1.0
Main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final keyApplicationId = 'xxxxxxxxx';
final keyParseServerUrl = 'https://parseapi.back4app.com';
final keyClientkey = 'xxxxxxxxxx';
await Parse().initialize(
keyApplicationId,
keyParseServerUrl,
clientKey: keyClientkey,
debug: true
);
print('connected to Parse Server');
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Future<bool> hasUserLogged() async {
print('future called');
ParseUser currentUser = await ParseUser.currentUser() as ParseUser;
if (currentUser == null) {
return false;
}
//Validates that the user's session token is valid
final ParseResponse parseResponse =
await ParseUser.getCurrentUserFromServer(
currentUser.get<String>('sessionToken'));
if (!parseResponse.success) {
print('call failed');
//Invalid session. Logout
await currentUser.logout();
return false;
} else {
print('user found');
return true;
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter - Parse Server',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: FutureBuilder<bool>(
future: hasUserLogged(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Scaffold(
body: Center(
child: Container(
width: 100,
height: 100,
child: CircularProgressIndicator()),
),
);
default:
if (snapshot.hasData && snapshot.data) {
print('to User');
return UserPage();
} else {
print('to Login');
return LoginPage();
}
}
}),
);
}
}
Log In Page:
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final controllerUsername = TextEditingController();
final controllerPassword = TextEditingController();
bool isLoggedIn = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter - Parse Server'),
),
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
height: 200,
child: Image.network(
'https://blog.back4app.com/wp-content/uploads/2017/11/logo-b4a-1-768x175-1.png'),
),
Center(
child: const Text('Flutter on Back4App',
style:
TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
),
SizedBox(
height: 16,
),
TextField(
controller: controllerUsername,
enabled: !isLoggedIn,
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.none,
autocorrect: false,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black)),
labelText: 'Username'),
),
SizedBox(
height: 8,
),
TextField(
controller: controllerPassword,
enabled: !isLoggedIn,
obscureText: true,
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.none,
autocorrect: false,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black)),
labelText: 'Password'),
),
SizedBox(
height: 16,
),
Container(
height: 50,
child: ElevatedButton(
child: const Text('Login'),
onPressed: isLoggedIn ? null : () => doUserLogin(),
),
),
SizedBox(
height: 16,
),
Container(
height: 50,
child: ElevatedButton(
child: const Text('Sign Up'),
onPressed: () => navigateToSignUp(),
),
),
SizedBox(
height: 16,
),
Container(
height: 50,
child: ElevatedButton(
child: const Text('Reset Password'),
onPressed: () => navigateToResetPassword(),
),
)
],
),
),
));
}
void doUserLogin() async {
final username = controllerUsername.text.trim();
final password = controllerPassword.text.trim();
final user = ParseUser(username, password, null);
var response = await user.login();
if (response.success) {
navigateToUser();
} else {
Message.showError(context: context, message: response.error.message);
}
}
void navigateToUser() {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => UserPage()),
(Route<dynamic> route) => false,
);
}
void navigateToSignUp() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SignUpPage()),
);
}
void navigateToResetPassword() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ResetPasswordPage()),
);
}
}
Home Page:
class UserPage extends StatelessWidget {
ParseUser currentUser;
Future<ParseUser> getUser() async {
currentUser = await ParseUser.currentUser() as ParseUser;
return currentUser;
}
#override
Widget build(BuildContext context) {
void doUserLogout() async {
var response = await currentUser.logout();
if (response.success) {
Message.showSuccess(
context: context,
message: 'User was successfully logout!',
onPressed: () {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => LoginPage()),
(Route<dynamic> route) => false,
);
});
} else {
Message.showError(context: context, message: response.error.message);
}
}
return Scaffold(
appBar: AppBar(
title: Text('User logged in - Current User'),
),
body: FutureBuilder<ParseUser>(
future: getUser(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Center(
child: Container(
width: 100,
height: 100,
child: CircularProgressIndicator()),
);
break;
default:
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(child: Text('Hello, ${snapshot.data.username}')),
SizedBox(
height: 16,
),
Container(
height: 50,
child: ElevatedButton(
child: const Text('Logout'),
onPressed: () => doUserLogout(),
),
),
],
),
);
}
}));
}
}
when using the app Logging in works fine but when I reload the app it just "ParseServer.currentUser()" in "MyApp" class returns null and sends me to the log in screen. I would appreciate the help if anyone knows what I did wrong.
also the FutureBuilder in "MyApp" class calls the Future twice I'm not sure why and if that may have something to do with it.
For each state of the main Future builder, all the widgets bellow will be recreating, the build function will be called again, and will call again the function inside Future.Builder.
you can use AsyncMemoizer to avoid it. AsyncMemoizer will make a Future be called only once.
You have many ways to solve this.
You can use a StatefullWidget for example and fetch the data on initState() it will be called only once, and the state will remain even if the parent rebuild.
And the best practice would create other layers to fetch your data, and your view is just responsible to show, not to fetch it.
https://pub.dev/packages/async
https://pub.dev/documentation/async/latest/async/AsyncMemoizer-class.html

I want to view the PDF file when the user taps on the cover

I want to view the PDF file when the user taps on the cover. I am new to Flutter.
Can you guys find out whats wrong in my code? When I tap on the book, it does nothing.
I think the problem is in the function for the PDF Viewer.
I am using advance_pdf_viewer 1.1.6.
class Books extends StatefulWidget {
#override
_BooksState createState() => _BooksState();
}
class _BooksState extends State<Books> {
bool _isLoading = true;
PDFDocument document;
var url;
#override
void initState() {
super.initState();
loadDocument();
}
loadDocument() async {
document = await PDFDocument.fromURL(url);
setState(() => _isLoading = false);
}
changePDF(value) async {
setState(() => _isLoading = true);
if (value == 1) {
document = await PDFDocument.fromURL(url);
} else {
print('nothing');
}
setState(() => _isLoading = false);
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: Firestore.instance.collection('books').snapshots(),
builder: (
context,
snapshot,
) {
if (snapshot.data == null)
return Center(
child: CircularProgressIndicator(
backgroundColor: Colors.red,
valueColor: new AlwaysStoppedAnimation<Color>(Colors.teal),
),
);
return GridView.builder(
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, childAspectRatio: 0.7),
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.all(8.0),
child: GridTile(
child: InkWell(
onTap: () async {
PDFDocument.fromURL(snapshot.data.documents[index]['url']);
_isLoading
? Center(child: CircularProgressIndicator())
: PDFViewer(document: document);
},
child: Container(
height: 200,
width: 110,
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.red[500].withOpacity(0.6),
spreadRadius: 0.5,
blurRadius: 1,
offset: Offset(2, 0),
),
],
color: Colors.white,
borderRadius: BorderRadius.circular(3),
border: Border.all(
style: BorderStyle.solid,
color: Colors.red[500],
width: 0.3)),
child: Column(children: <Widget>[
Padding(
padding: const EdgeInsets.all(5.0),
child: Container(
child: Image.network(
snapshot.data.documents[index]['image'],
width: 100,
),
),
),
SizedBox(height: 5),
Text(
snapshot.data.documents[index]['name'],
)
]),
),
),
),
),
);
});
}
}
PDFViewer returns a Widget. If you want to view the pdf file when tapping on the InkWell you need to make a widget that displays the widget that PDFViewer returns, e.g.,
class PDFScreen extends StatelessWidget {
PDFDocument document;
PDFScreen({#required this.document});
#override
Widget build(BuildContext context) {
return Scaffold(
child: PDFViewer(document: document)
);
}
}
And change the onTap() of the InkWell to:
onTap: () async {
PDFDocument.fromURL(snapshot.data.documents[index]['url']);
_isLoading
? Center(child: CircularProgressIndicator())
: Navigator.push(context, MaterialPageRoute(builder: (context) => PDFScreen(document: document)));
},

Flutter - Returning to previous page from AppBar is not refreshing the page, with Navigator.pop(context)

I was trying to get the list page refreshed if a method was run on another page. I do pass the context using the push navigation.
I tried to follow these 3 answers Answer 1 Answer 2 and Answer 3 and I am not able to manage the states here.
This is the first list page which needs to be refreshed. It calls a class
class _PageLocalState extends State<PageLocal> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: SafeArea(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: widget.allLocal.length,
//padding: const EdgeInsets.only(top: 10.0),
itemBuilder: (context, index) {
return LocalCard(widget.allLocal[index]);
},
)),
)
],
),
);
}
}
The next class:
class LocalCardState extends State<LocalCard> {
FavData localdet;
LocalCardState(this.localdet);
ListTile makeListTile() => ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
title: Text(
localdet.name,
style: TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(localdet.loc),
trailing: Icon(Icons.keyboard_arrow_right, size: 30.0),
onTap: () => navigateToDetail(localdet),
);
Widget get localCard {
return new Card(
elevation: 4.0,
margin: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
child: Container(
child: makeListTile(),
));
}
#override
Widget build(BuildContext context) {
return new Container(
child: localCard,
);
}
navigateToDetail(FavData localdet) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FavouriteDetailPage(
mndet: localdet,
)));
setState(() {});
}
}
Now this is routing to the final detail page:
class _FavouriteDetailPageState extends State<FavouriteDetailPage> {
bool isFav = false;
FavData mndet;
_FavouriteDetailPageState(this.mndet);
// reference to our single class that manages the database
final dbHelper = DatabaseHelper.instance;
#override
Widget build(BuildContext context) {
Widget heading = new Container(...);
Widget middleSection = new Expanded(...);
Widget bottomBanner = new Container(...);
Widget body = new Column(...);
final makeBottom = Container(
height: 55.0,
child: BottomAppBar(
color: Color.fromRGBO(36, 36, 36, 1.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new FavIconWidget(mndet),
],
),
),
);
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('The Details'),
backgroundColor: Color.fromRGBO(36, 36, 36, 1.0),
),
body: Container(
child: Card(
elevation: 5.0,
shape: RoundedRectangleBorder(
side: BorderSide(color: Colors.white70, width: 1),
borderRadius: BorderRadius.circular(10),
),
margin: EdgeInsets.all(20.0),
child: Padding(
padding: new EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
child: body,
),
),
),
bottomNavigationBar: makeBottom,
);
}
void share(BuildContext context, FavData mndet) {
final RenderBox box = context.findRenderObject();
final String shareText = "${mndet.name} - ${mndet.desc}";
Share.share(shareText,
subject: mndet.loc,
sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size);
}
}
class FavIconWidget extends StatefulWidget {
final FavData mnforIcon;
FavIconWidget(this.mnforIcon);
#override
_FavIconWidgetState createState() => _FavIconWidgetState();
}
class _FavIconWidgetState extends State<FavIconWidget> {
final dbHelper = DatabaseHelper.instance;
Future<bool> get isFav async {
final rowsPresent = await dbHelper.queryForFav(widget.mnforIcon.id);
if (rowsPresent > 0) {
print('Card Loaded - Its Favourite already');
return false;
} else {
print('Card Loaded - It is not favourite yet');
return true;
}
}
void _insert() async {...}
void _delete() async {...}
#override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
future: isFav,
initialData:
false, // you can define an initial value while the db returns the real value
builder: (context, snapshot) {
if (snapshot.hasError)
return const Icon(Icons.error,
color: Colors.red); //just in case the db return an error
if (snapshot.hasData)
return IconButton(
icon: snapshot.data
? const Icon(Icons.favorite_border, color: Colors.white)
: Icon(Icons.favorite, color: Colors.red),
onPressed: () => setState(() {
if (!snapshot.data) {
print('Its favourite so deleting it.');
_delete();
} else {
print('Wasnt fav in the first place so inserting.');
_insert();
}
}));
return CircularProgressIndicator(); //if there is no initial value and the future is not yet complete
});
}
}
I am sure this is just some silly coding I have done but just not able to find out. Where.
I tried adding Navigator.pop(context); in different sections of the detail page and it fails.
Currently, I have to navigate back to the Favourites list page and then HomePage and then back to Favourites ListPage to refresh the list.
try this.. Anywhere you are using Navigator.pop or Navigator.push .. Instead of this use this:
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (BuildContext context) => Password())
);
//instead of Password use the name of the page(the second page you want to go to)

StreamBuilder snapshot.hasError show many times when keyboard show/hide flutter

I have a login screen and I'm using BloC pattern, but when i click on button the validation fails, the message from error is called many times, because the stream builder snapshot.error has a value, i don't know how change this to show error only when the user click at the button and validation in fact throw an error.
class LoginPage extends StatefulWidget {
static String tag = 'login-page';
#override
State<StatefulWidget> createState() => LoginState();
}
class LoginState extends State<LoginPage> {
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
#override
Widget build(BuildContext context) {
LoginBloc loginBloc = BlocProvider.of(context).loginBloc;
return Scaffold(
body: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(16.0),
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
Colors.blueAccent,
Colors.blue,
]),
),
child: Center(
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0))),
elevation: 4.0,
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 16.0, right: 16.0),
children: <Widget>[
/*_logo(),*/
SizedBox(height: 24.0),
_emailField(loginBloc),
SizedBox(height: 8.0),
_passwordField(loginBloc),
SizedBox(height: 24.0),
_loginButtonSubmit(loginBloc),
_loading(loginBloc),
_error(loginBloc),
_success(loginBloc),
_settingsText()
],
),
),
)),
);
}
Widget _logo() {
return Hero(
tag: 'hero',
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0.0),
child: Center(
child: Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fill,
image: AssetImage('assets/4.0x/ic_launcher.png'),
),
borderRadius: BorderRadius.all(Radius.circular(50.0)),
),
),
),
),
);
}
Widget _emailField(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.emailStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
//Anytime the builder sees new data in the emailStream, it will re-render the TextField widget
return TextField(
onChanged: loginBloc.setEmail,
keyboardType: TextInputType.emailAddress,
controller: _usernameController,
decoration: InputDecoration(
labelText: 'Usuário',
errorText: snapshot
.error, //retrieve the error message from the stream and display it
),
);
},
);
}
Widget _passwordField(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.passwordStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
return TextField(
onChanged: loginBloc.setPassword,
obscureText: true,
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Senha',
errorText: snapshot.error,
),
);
},
);
}
Widget _loginButtonSubmit(LoginBloc loginBloc) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
onPressed: () {
loginBloc.submit(
LoginRequest(_usernameController.text, _passwordController.text));
},
padding: EdgeInsets.all(12),
color: Colors.blue,
child: Text('Entrar', style: TextStyle(color: Colors.white)),
),
);
}
Widget _loading(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.loadingStream,
initialData: false,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return Center(
child: snapshot.data
? Padding(
padding: const EdgeInsets.all(16.0),
child: CircularProgressIndicator(),
)
: null,
);
});
}
Widget _error(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.successStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasError) {
_onWidgetDidBuild(() {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('${snapshot.error}'),
backgroundColor: Colors.red,
));
});
}
return Container();
});
}
Widget _success(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.successStream,
initialData: null,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData && snapshot.data.erro == 0) {
_onWidgetDidBuild(() {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => HomePage()));
});
}
return Container();
});
}
Widget _settingsText() {
return Center(
child: GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => LoginSettingsPage()));
},
child: Padding(
padding: EdgeInsets.fromLTRB(16.0, 0, 16.0, 16.0),
child: Text(
"Configurações",
style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
),
),
),
);
}
void _onWidgetDidBuild(Function callback) {
WidgetsBinding.instance.addPostFrameCallback((_) {
callback();
});
}
}
Bloc
class LoginBloc with Validator {
//RxDart's implementation of StreamController. Broadcast stream by default
final _emailController = BehaviorSubject<String>();
final _passwordController = BehaviorSubject<String>();
final _loadingController = BehaviorSubject<bool>();
final _successController = BehaviorSubject<LoginResponse>();
final _submitController = PublishSubject<LoginRequest>();
//Return the transformed stream
Stream<String> get emailStream => _emailController.stream.transform(performEmptyEmailValidation);
Stream<String> get passwordStream => _passwordController.stream.transform(performEmptyPasswordValidation);
Stream<bool> get loadingStream => _loadingController.stream;
Stream<LoginResponse> get successStream => _successController.stream;
//Add data to the stream
Function(String) get setEmail => _emailController.sink.add;
Function(String) get setPassword => _passwordController.sink.add;
Function(LoginRequest) get submit => _submitController.sink.add;
LoginBloc() {
_submitController.stream.distinct().listen((request) {
_login(request.username, request.password);
});
}
_login(String useName, String password) async {
_loadingController.add(true);
ApiService.login(useName, password).then((response) {
if (response.erro == 0) {
saveResponse(response);
} else {
final error = Utf8Codec().decode(base64.decode(response.mensagem));
_successController.addError(error);
print(error);
}
_loadingController.add(false);
}).catchError((error) {
print(error);
_loadingController.add(false);
_successController.addError("Falha ao realizar login!");
});
}
saveResponse(LoginResponse response) {
SharedPreferences.getInstance().then((preferences) async {
var urlSaved = await preferences.setString(
Constants.LOGIN_RESPONSE, response.toJson().toString());
if (urlSaved) {
_successController.add(response);
}
}).catchError((error) {
_successController.addError(error);
});
}
dispose() {
_emailController.close();
_passwordController.close();
_loadingController.close();
_successController.close();
_submitController.close();
}
}
I found the solution to my error when in click in InputField and focus change, the StreamBuilder rebuild de widget e show again the error every time.
I just put a validation before start shows the error to consider the state of the snapshot.
Widget _error(LoginBloc loginBloc) {
return StreamBuilder(
stream: loginBloc.successStream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.active &&
snapshot.hasError) {
_onWidgetDidBuild(() {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('${snapshot.error}'),
backgroundColor: Colors.red,
));
});
}
return Container();
});
}
If is active is because I throw an error in my Bloc class, if not, is because the stream builder was rebuilt the widget. This solves my problem.
I don't know if it is the better solution but solves my problem at the moment.
I had same problem, adding initialdata null works fine for me.
return new StreamBuilder(
stream: _bloc.stream,
initialData: null,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
According Flutter Docs hasError validate null value.