How to use a variable within a method elsewhere in Flutter? - flutter

I know this is a stupid question, but I'm asking as a newbie to flutter.
I created a getData() method to call Firebase's User data and display it on the app. And to call it, result.data() is saved as a variable name of resultData.
But as you know I can't use Text('user name: $resultData'). How do I solve this? It's a difficult problem for me, since I don't have any programming basics. thank you.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:shipda/screens/login/login_screen.dart';
import 'package:get/get.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _authentication = FirebaseAuth.instance;
User? loggedUser;
final firestore = FirebaseFirestore.instance;
void getData() async {
var result = await firestore.collection('user').doc('vUj4U27JoAU6zgFDk6sSZiwadQ13').get();
final resultData = result.data();
}
#override
void initState() {
super.initState();
getCurrentUser();
getData();
}
void getCurrentUser(){
try {
final user = _authentication.currentUser;
if (user != null) {
loggedUser = user;
print(loggedUser!.email);
}
} catch (e){
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: [
Text('Home Screen'),
IconButton(
onPressed: () {
FirebaseAuth.instance.signOut();
Get.to(()=>LoginScreen());
},
icon: Icon(Icons.exit_to_app),
),
IconButton(
onPressed: () {
Get.to(() => LoginScreen());
},
icon: Icon(Icons.login),
),
Text('UserInfo'),
Text('user name: ')
],
),
),
);
}
}

What you are referring to is called state.
It is a complex topic and you will have to start studying it to correctly develop any web based app.
Anyway, as for your situation, you should have resultData be one of the attributes of the _HomeScreenState class.
Then change resultData in a setState method, like this:
setState(() {
resultData = result.data();
});
Then, in the Text widget, you can actually do something like:
Text("My data: " + resultData.ToString())
Instead of ToString of course, use anything you need to actually access the data.

By writing
void getData() async {
var result = await firestore.collection('user').doc('vUj4U27JoAU6zgFDk6sSZiwadQ13').get();
final resultData = result.data();
}
you make resultData only local to the function getData(). You should declare it outside. Also you need to put it in a setState to make it rebuild the screen after loading. I don't know what type it is, but if it's a String for example you could write
String? resultData;
void getData() async {
var result = await firestore.collection('user').doc('vUj4U27JoAU6zgFDk6sSZiwadQ13').get();
setState(() {
resultData = result.data();
});
}
Then you can use Text('user name: $resultData') for example

Related

Flutter: How can I access element from API in my UI

I successfully get data from API. I test it using the Print command and the element displayed successfully in the console. When I tried to access the same element in TEXT Widget it displays null.
Here is the code:
import 'package:api_practice/worker/data.dart';
import 'package:flutter/material.dart';
import 'package:api_practice/worker/data.dart';
class WeatherHome extends StatefulWidget {
#override
_WeatherHomeState createState() => _WeatherHomeState();
}
class _WeatherHomeState extends State<WeatherHome> {
TextEditingController searchcont = TextEditingController();
DataClass datainstace = DataClass();
void data() async {
await datainstace.getcurrentlocation();
await datainstace.getdata();
print(datainstace.area);
}
#override
void initState() {
data();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:Text(
("${datainstace.area}"),
style: TextStyle(fontSize: 25, color: Colors.white),
),
);
}
}
output on console: output in console
App looks like: appscreen
In addition to the previous answers, you can use a value listenable builder, this improves performance and thus it is not necessary to use a setState.
You can check the following link to obtain information on how to use it
Explore ValueListenableBuilder in Flutter
You should use setState method in data function. Because your build method is finishing before your async method.
Use it like this:
import 'package:api_practice/worker/data.dart';
import 'package:flutter/material.dart';
import 'package:api_practice/worker/data.dart';
class WeatherHome extends StatefulWidget {
#override
_WeatherHomeState createState() => _WeatherHomeState();
}
class _WeatherHomeState extends State<WeatherHome> {
TextEditingController searchcont = TextEditingController();
DataClass datainstace = DataClass();
void data() async {
await datainstace.getcurrentlocation();
await datainstace.getdata();
print(datainstace.area);
setState(() {});
}
#override
void initState() {
data();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:Text(
"${datainstace.area}",
style: TextStyle(fontSize: 25, color: Colors.white),
),
);
}
}
when you want to update the state of your widget, you have to call the setState function to make the widget re-render with the changes.
void data() {
setState(() async {
await datainstace.getcurrentlocation();
await datainstace.getdata();
print(datainstace.area);
});
}

InitState() not finishing before Build method is called

I'm having a problem in my home_screen.dart file. I
have a method called pullUserData() that is called in initState() but before pullUserData() is completely finished, the build method in home_screen.dart begins. This results in null values (auth and friendsList) being sent to NavDrawer() and FriendsFeed() in the build method of home_screen.dart.
How can I prevent NavDrawer() and FriendsFeed() from being called in the build method before initState() is completely finished? Should I use FutureBuilder?
User_data.dart handles gets the values for auth and friendsList.
home_screen.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:mood/components/friends_feed.dart';
import 'package:mood/components/nav_drawer.dart';
import 'package:mood/services/user_data.dart';
class LandingScreen extends StatefulWidget {
static const String id = 'landing_screen';
#override
_LandingScreenState createState() => _LandingScreenState();
}
class _LandingScreenState extends State<LandingScreen> {
FirebaseAuth auth;
List<dynamic> friendsList;
#override
void initState() {
super.initState();
pullUserData();
}
Future<void> pullUserData() async {
UserData userData = UserData();
await userData.getUserData();
auth = userData.auth;
friendsList = userData.friendsList;
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('Mood'),
centerTitle: true,
),
drawer: NavDrawer(auth),
body: FriendsFeed(friendsList),
);
}
}
user_data.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class UserData {
final FirebaseAuth _auth = FirebaseAuth.instance;
User _currentUser;
String _currentUserUID;
List<dynamic> _friendsList;
FirebaseAuth get auth => _auth;
User get currentUser => _currentUser;
String get currentUserUID => _currentUserUID;
List<dynamic> get friendsList => _friendsList;
Future<void> getUserData() async {
getCurrentUser();
getCurrentUserUID();
await getFriendsList();
}
void getCurrentUser() {
_currentUser = _auth.currentUser;
}
void getCurrentUserUID() {
_currentUserUID = _auth.currentUser.uid;
}
Future<void> getFriendsList() async {
await FirebaseFirestore.instance
.collection("Users")
.doc(_currentUserUID)
.get()
.then((value) {
_friendsList = value.data()["friends"];
});
}
}
There are couple of problems in your code but it will work.
Firstly, if you want to set value of your friendslist during build, you have to use setState like this:
setState(() {
friendsList = userData.friendsList;
});
And if you want to wait until pullUserData() finish, you are looking for something called splash screen, but in your problem, you are waiting only for body to be build so I will recommend to use progress indicator in your scaffold like this:
return Scaffold(
appBar: Bars.menuAppBar('Devices'),
drawer: DrawerMenu(),
backgroundColor: ColorThemes.backgroundColor,
body: _loading
? Center(
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(
Colors.blue), //choose your own color
))
: FriendsFeed(friendsList)
);
You can see that I used _loading variable. You will have to define it before your initState() like
bool _loading = true;
Then after you set your friendsList inside of your pullUserData() function, change _loading to false inside of setState just like this:
setState(() {
friendsList = userData.friendsList;
_loading = false;
});

Flutter Secure Storage Change Route

I have successfully implemented the flutter_secure_storage in my flutter project, when the user writes his email and password, it get's stored, but here is the thing I don't understand. How should I setup screen routes depending if the user has already logged in or not. If it is the same user, so the username and pass are stored in the secure_storage, I want him to go directly to HomeScreen(), but if there is a new user that needs to log in, so there is no data in the secure_storage, then I want him sent to LoginScreen(). I have done this so far:
import 'dart:async';
import 'package:flutter/material.dart';
import 'login_screen.dart';
import 'home_screen.dart';
import 'components/alarm_buttons.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class WelcomeScreen extends StatefulWidget {
static const String id = 'welcome_screen';
#override
_WelcomeScreenState createState() => _WelcomeScreenState();
}
class _WelcomeScreenState extends State<WelcomeScreen> {
void readData() async {
final storage = FlutterSecureStorage();
String myPassword = await storage.read(key: "p");
String myEmail = await storage.read(key: "e");
print(myEmail);
print(myPassword);
}
#override
void initState() {
final storage = FlutterSecureStorage();
Timer(
Duration(seconds: 2),
() => Navigator.pushNamed(
context,
storage == null ? LoginScreen.id : HomePage.id,
));
readData();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
AlarmButtons(
buttonColour: Colors.grey,
buttonText: 'Log In',
buttonTextColour: Colors.white,
onButtonPress: () {
Navigator.pushNamed(context, LoginScreen.id);
},
),
AlarmButtons(
buttonColour: Colors.white,
buttonText: 'Sign up',
buttonTextColour: Colors.grey,
onButtonPress: () {
Navigator.pushNamed(context, SignUpScreen.id);
},
),
],
),
),
);
}
}
Now the problem starts when I want to return to the Welcome Screen (the starting page of my app shown above), naturally it triggers the initState again and I get back to the HomePage() again. How can I dispose of that, only triggering that initState when the app starts, so after automatic login I can return to the Welcome Screen without triggering it?
Thanks in advance!
You should initial start something like a SplashScreen (or WelcomeScreen in your case). There you can decide to which screen you want to navigate based on the saved data. Example:
class SplashScreen extends StatefulWidget {
const SplashScreen({Key key}) : super(key: key);
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
_startApp();
super.initState();
}
Future<void> _startApp() async {
final storage = FlutterSecureStorage();
String myEmail = await storage.read(key: "e");
if (myEmail == null) {
// TODO Navigate to Login Screen
} else {
// TODO Navigate to Home Screen
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("Splashscreen"),
),
);
}
}

Displaying a PDF in Flutter

I'm brand new to Flutter and am struggling to display a PDF from a Base64 string. I can get it to work, but only with a pointless UI sitting between the list and the PDF.
The app simply crashes with 'Lost connection to device' and no other information appears in the Android Studio debug console.
So, this works:
import 'dart:developer';
import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_full_pdf_viewer/full_pdf_viewer_scaffold.dart';
import 'package:path_provider/path_provider.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class PdfBase64Viewer extends StatefulWidget {
final DocumentSnapshot document;
const PdfBase64Viewer(this.document);
#override
_PdfBase64ViewerState createState() => new _PdfBase64ViewerState();
}
class _PdfBase64ViewerState extends State<PdfBase64Viewer> {
String pathPDF = "";
#override
void initState() {
super.initState();
createFile().then((f) {
setState(() {
pathPDF = f.path;
print("Local path: " + pathPDF);
});
});
}
Future<File> createFile() async {
final filename = widget.document.data()['name'];
var base64String = widget.document.data()['base64'];
var decodedBytes = base64Decode(base64String.replaceAll('\n', ''));
String dir = (await getApplicationDocumentsDirectory()).path;
File file = new File('$dir/$filename');
await file.writeAsBytes(decodedBytes.buffer.asUint8List());
return file;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('This UI is pointless')),
body: Center(
child: RaisedButton(
child: Text("Open PDF"),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => PDFScreen(pathPDF)),
),
),
),
);
}
}
class PDFScreen extends StatelessWidget {
String pathPDF = "";
PDFScreen(this.pathPDF);
#override
Widget build(BuildContext context) {
return PDFViewerScaffold(
appBar: AppBar(
title: Text("Document"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.share),
onPressed: () {},
),
],
),
path: pathPDF);
}
}
But this doesn't:
import 'dart:developer';
import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_full_pdf_viewer/full_pdf_viewer_scaffold.dart';
import 'package:path_provider/path_provider.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class PdfBase64Viewer extends StatefulWidget {
final DocumentSnapshot document;
const PdfBase64Viewer(this.document);
#override
_PdfBase64ViewerState createState() => new _PdfBase64ViewerState();
}
class _PdfBase64ViewerState extends State<PdfBase64Viewer> {
String pathPDF = "";
#override
void initState() {
super.initState();
createFile().then((f) {
setState(() {
pathPDF = f.path;
print("Local path: " + pathPDF);
});
});
}
Future<File> createFile() async {
final filename = widget.document.data()['name'];
var base64String = widget.document.data()['base64'];
var decodedBytes = base64Decode(base64String.replaceAll('\n', ''));
String dir = (await getApplicationDocumentsDirectory()).path;
File file = new File('$dir/$filename');
await file.writeAsBytes(decodedBytes.buffer.asUint8List());
return file;
}
#override
Widget build(BuildContext context) {
return PDFViewerScaffold(
appBar: AppBar(
title: Text("Document"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.share),
onPressed: () {},
),
],
),
path: pathPDF);
}
}
Can anyone help me understand why?
You should pay more attention to what the output is (or isn't), it will make your debugging process a lot faster and without needing SO most of the time.
The app simply crashes with 'Lost connection to device' and no other
information appears in the Android Studio debug console.
So that means you aren't seeing print("Local path: " + pathPDF); Which means your app hasn't made it that far.
As to why it is crashing, welp without any testing I can see you aren't handling your asyncs and futures properly. You are just assuming they will be ok and you are assuming they will finish quickly (before the widget gets built). Your PDFViewerScaffold is most probably getting an empty string. A simple check can fix that:
#override
Widget build(BuildContext context) {
return (pathPDF == null || pathPDF.isEmpty) ? Center(child: CircularProgressIndicator())
: PDFViewerScaffold( //....
For a really nice and clean code you should move pdf generation stuff into a separate class (or at least file) and you should also take into consideration how much time is acceptable for such a task (timeout).
PS The reason why your code works with "pointless UI" is because your fingers are slower than file generation, by the time you've tapped on RaisedButton the file has been already created and luckily has a path. That first code also doesn't do any checks, so if file creation fails for whatever reason you'd most probably end up with a crash or a blank screen.
EDIT: Bellow is a full example of how I would go about doing this. It should make your app a lot more resilient to crashes.
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'package:flutter_full_pdf_viewer/full_pdf_viewer_scaffold.dart';
import 'package:path_provider/path_provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: PdfBase64Viewer(),
);
}
}
Future<File> createFile(String base64String) async {
final fileName = "test";
final fileType = "pdf";
var decodedBytes = base64Decode(base64String);
String dir = (await getApplicationDocumentsDirectory()).path;
// uncomment the line bellow to trigger an Exception
// dir = null;
File file = new File('$dir/$fileName.$fileType');
// uncomment the line bellow to trigger an Error
// decodedBytes = null;
await file.writeAsBytes(decodedBytes.buffer.asUint8List());
// uncomment the line bellow to trigger the TimeoutException
// await Future.delayed(const Duration(seconds: 6),() async {});
return file;
}
class PdfBase64Viewer extends StatefulWidget {
const PdfBase64Viewer();
#override
_PdfBase64ViewerState createState() => new _PdfBase64ViewerState();
}
class _PdfBase64ViewerState extends State<PdfBase64Viewer> {
bool isFileGood = true;
String pathPDF = "";
/*
Bellow is a b64 pdf, the smallest one I could find that has some content and it fits within char limit of StackOverflow answer.
It might not be 100% valid, ergo some readers might not accept it, so I recommend swapping it out for a proper PDF.
Output of the PDF should be a large "Test" text aligned to the center left of the page.
source: https://stackoverflow.com/questions/17279712/what-is-the-smallest-possible-valid-pdf/17280876
*/
String b64String = """JVBERi0xLjIgCjkgMCBvYmoKPDwKPj4Kc3RyZWFtCkJULyA5IFRmKFRlc3QpJyBFVAplbmRzdHJlYW0KZW5kb2JqCjQgMCBvYmoKPDwKL1R5cGUgL1BhZ2UKL1BhcmVudCA1IDAgUgovQ29udGVudHMgOSAwIFIKPj4KZW5kb2JqCjUgMCBvYmoKPDwKL0tpZHMgWzQgMCBSIF0KL0NvdW50IDEKL1R5cGUgL1BhZ2VzCi9NZWRpYUJveCBbIDAgMCA5OSA5IF0KPj4KZW5kb2JqCjMgMCBvYmoKPDwKL1BhZ2VzIDUgMCBSCi9UeXBlIC9DYXRhbG9nCj4+CmVuZG9iagp0cmFpbGVyCjw8Ci9Sb290IDMgMCBSCj4+CiUlRU9G""";
#override
void initState() {
super.initState();
Future.delayed(Duration.zero,() async {
File tmpFile;
try {
tmpFile = await createFile(b64String).timeout(
const Duration(seconds: 5));
} on TimeoutException catch (ex) {
// executed when an error is a type of TimeoutException
print('PDF taking too long to generate! Exception: $ex');
isFileGood = false;
} on Exception catch (ex) {
// executed when an error is a type of Exception
print('Some other exception was caught! Exception: $ex');
isFileGood = false;
} catch (err) {
// executed for errors of all types other than Exception
print("An error was caught! Error: $err");
isFileGood = false;
}
setState(() {
if(tmpFile != null){
pathPDF = tmpFile.path;
}
});
});
}
#override
Widget build(BuildContext context) {
return (!isFileGood) ? Scaffold(body: Center(child: Text("Something went wrong, can't display PDF!")))
: (pathPDF == null || pathPDF.isEmpty) ? Scaffold(body: Center(child: CircularProgressIndicator()))
: PDFViewerScaffold(
appBar: AppBar(
title: Text("Document"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.share),
onPressed: () {},
),
],
),
path: pathPDF);
}
}

OnSharedPreferenceChangeListener for Flutter

In Android, you can do the following to listen to shared preference change
SharedPreferences.OnSharedPreferenceChangeListener spChanged = new
SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
// your stuff here
}
};
Is it possible to do this using flutter? I have read through the official flutter shared_preference and this features seems not yet implemented.
Is there any other library or ways to achieve the above without diving into native code. Thanks.
You can easily "listen" to SharedPreferences using a package like flutter_riverpod.
Initialize sharedPreferences
SharedPreferences? sharedPreferences;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
sharedPreferences = await SharedPreferences.getInstance();
runApp(const ProviderScope(child: MyApp()));
}
Create the stateProvider
import 'package:hooks_riverpod/hooks_riverpod.dart';
final keepOnTopProvider = StateProvider<bool>((ref) {
return sharedPreferences?.getBool('on_top') ?? true;
});
Update your UI when something changes
class SettingsView extends ConsumerWidget {
const SettingsView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
bool onTop = ref.watch(keepOnTopProvider);
return Scaffold(
appBar: AppBar(title: const Text('Settings'), centerTitle: false),
body: ListView(
padding: const EdgeInsets.symmetric(horizontal: 12),
children: [
SwitchListTile(
title: const Text('Keep on top'),
value: onTop,
onChanged: (value) async {
sharedPreferences?.setBool('on_top', value);
ref.read(keepOnTopProvider.notifier).state = value;
await windowManager.setAlwaysOnTop(value);
},
),
],
),
);
}
}
As a work around, add the following codes to your main():
void funTimerMain() async {
// here check any changes to SharedPreferences, sqflite, Global Variables etc...
if (bolAnythingChanged) {
// do something
// 'refresh' any page you want (below line using Redux as example)
GlobalVariables.storeHome.dispatch(Actions.Increment);
}
// recall this timer every x milliseconds
new Future.delayed(new Duration(milliseconds: 1000), () async {
funTimerMain();
});
}
// call the timer for the first time
funTimerMain();