How to display SharedPreferences value in Text widget in flutter? - flutter

The task is to get value from SharedPrefrences and display it in the Text widget.
Here I tried to store the value in the variable _userCurrency and display it in ListTile. The problem seems that the ListTile loads quickly with the value "null".
Even getUserCurrencySymbol() isn't returning any value. I have already tried
return MySharedPreferences.instance.getUserCurrencyValue('userCurrency'); and other possible solutions.
menu.dart
late String _userCurrency;
getUserCurrencySymbol().then((value) {_userCurrency = value.toString();});
return StatefulBuilder(
builder: (BuildContext context, void Function(void Function()) setState) {
return ListView(
children: [
ListTile(title: Text(_userCurrency)),
]
) //ListView
},
); //Stateful builder
controller.dart
Future<String> getUserCurrencySymbol() async {
return MySharedPreferences.instance.getUserCurrencyValue('userCurrency').then((value) {return value.toString();});
}
class MySharedPreferences {
MySharedPreferences._privateConstructor();
static final MySharedPreferences instance = MySharedPreferences._privateConstructor();
setUserCurrencyValue(String key, String value) async {
SharedPreferences instance = await SharedPreferences.getInstance();
instance.setString(key, value);
}
getUserCurrencyValue(String key) async {
SharedPreferences instance = await SharedPreferences.getInstance();
return instance.getString(key) ?? "Bitcoin";
}

You should use setState to update the ui when the data is loaded.
getUserCurrencySymbol().then((value) {
setState((){
_userCurrency = value.toString();
});
});

You can use FutureBuilder to load data and handle loading/error states
FutureBuilder<String>(
future: getUserCurrencySymbol(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if(snapshot.hasError) {
//fixme just to check an error
print("Error: ${snapshot.error}");
return Text("Error");//or what you want
}
if (!snapshot.hasData) {
return CircularProgressIndicator();//or what you want
}
return ListView(
children: [
ListTile(title: Text(snapshot.data)),
]
);
},
)
And try to change getUserCurrencySymbol();
Future<String> getUserCurrencySymbol() {
return MySharedPreferences.instance.getUserCurrencyValue('userCurrency');
}

Related

Why does my async method run twice in Flutter?

I want to load a static list data when entering indexScreen,but the list sometimes show twice the same content,sometimes not.
This is my list setting:List<ListClass> listItems=List<ListClass>();,ListClass is a simple class with on different attributes and a constructor.
I use home:IndexScreen() in main.dart to show Index page.
return MaterialApp(
home: IndexScreen(),
debugShowCheckedModeBanner: false,
onGenerateRoute: router.generator,
builder: EasyLoading.init(),
);
And before this page build,it will update listItems using:
Future<bool> initUserAndIndex() async{
if (curUserEmail==null) sharedGetData(USER_EMAIL).then((value) => curUserEmail=value.toString());
print(curUserEmail);
await UserTable().getUserInfo(curUserEmail).then((value){print("user ok");});
await CollectionTable().getIndexList().then((value){print("Collection ok");return true;});
return null;
}
buildPage:
#override
Widget build(BuildContext context) {
return FutureBuilder<Object>(
future: initUserAndIndex(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState==ConnectionState.waiting)
{
EasyLoading.show(status: 'loading...');
// avoid no return,this cause a whiteborad transition,I don't know how to solve it,too.
return Container();
}
else
{
EasyLoading.dismiss();
return SafeArea(
child: Scaffold(
// the listItems is used in Body()
body: Body(),
),
);
}
},
);
}
}
I run this app,and it prints twice user ok and Collection ok.But when I use ROUTER.NAVIGETE,it only prints once.
User Information is OK,but the list is such a great problem--the page shows twice content
I put my code at an order of relevance of this prblom,I think.Next I put my the two awaited funtion here:
User:
Future<bool> getUserInfo(String userEmail) async{
await userCollection.where({'userEmail':userEmail}).get().then((res) async {
//assign to the static variables
return true;
});
return null;
}
Collection:
Future<bool> getIndexList() async {
listItems.clear();
await listCollection.get().then((value){
var v = value.data;
for (var data in v) {
//get data and package them,add after the listItems list.
listItems.add(ListClass(header, content, userId, favorCount, wordCount));
}
return true;
});
}
You probably want to assign your future in your widget class, but not in the build method as the documentation show, otherwise, everytime your build method is triggered, it will call again your FutureBuilder.
final Future<String> _calculation = Future<String>.delayed(
const Duration(seconds: 2),
() => 'Data Loaded',
);
#override
Widget build(BuildContext context) {
return DefaultTextStyle(
style: Theme.of(context).textTheme.headline2!,
textAlign: TextAlign.center,
child: FutureBuilder<String>(
future: _calculation, // a previously-obtained Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
// ...
}
),
),
}

BLoC is returning an empty snapshot. BLoC stream is from Sqflite

I'm trying to update my listview widget from BloC but it's always returning an empty snapshot.BLoC is using a stream from an async SQLite query using sqflite. The sqflite query appears to be working when accessed directly.
Here's a snippet of the UI portion:
ListBloc listBloc = ListBloc();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(child: getBoxList()),
FlatButton(
onPressed: () {
listBloc.queryBoxes();
},
child: Text('pull data'),
),
],
),
),
);
}
Widget getBoxList() {
return StreamBuilder(
stream: listBloc.boxes,
builder: (BuildContext context, AsyncSnapshot<List<Box>> snapshot) {
return getList(snapshot);
});
}
Widget getList(AsyncSnapshot<List<Box>> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Text(snapshot.data[index].boxName);
},
);
} else {
return Text('snapshot has no data');
}
}
Hers's a snippet of the BloC:
class ListBloc {
final _boxListController = StreamController<List<Box>>.broadcast();
final DatabaseHelper _db = DatabaseHelper.instance;
get boxes => _boxListController.stream;
ListBloc() {
getAllIBoxes();
}
getAllIBoxes() async {
_boxListController.sink
.add(await _db.queryAllBoxRows(DatabaseHelper.tableBoxes));
}
dispose() {
// _itemListController.close();
_boxListController.close();
}
}
_db.queryAllBoxRows(DatabaseHelper.tableBoxes) works just fine when tested outside the BLoC.
Thank you!
-- update --
here's the queryBoxes function in BLoC. I just added this to test if the query returns data.
queryBoxes() async {
DatabaseHelper _db = DatabaseHelper.instance;
final _boxes = await _db.queryAllBoxRows(DatabaseHelper.tableBoxes);
_boxes.forEach((element) {
print(element.boxName);
});
}
-- update 2 --
Source of the stream:
getAllIBoxes() async {
_boxListController.sink
.add(await _db.queryAllBoxRows(DatabaseHelper.tableBoxes));
}
is _db.queryAllBoxRows(DatabaseHelper.tableBoxes) which is a method in my DatabaseHelper that looks like this.
Future<List<Box>> queryAllBoxRows(table) async {
Database db = await instance.database;
final _result = await db.query(table);
List<Box> box = _result.map((e) => Box.fromDatabaseJson(e)).toList();
return box;
}
You are just printing the values in the queryBoxes function. You have to inform your stream that there is data available, for that you've to add the values to the sink.
Modify your function to:
queryBoxes() async {
DatabaseHelper _db = DatabaseHelper.instance;
final _boxes = await _db.queryAllBoxRows(DatabaseHelper.tableBoxes);
_boxes.forEach((element) {
print(element.boxName);
});
_boxListController.sink.add(boxes);
}

How to get a String from a Future<String> in Flutter?

I have a strange problem. I need the value of a Future<String> as String to display an image with CachedNetworkImage. Because I have to wait for a downloadUrl which comes from Firebase Storage I found no way to use it as normal String.
Here is my Widget where I need the value
Container(child: Obx(
() {
return CachedNetworkImage(
imageUrl: _getImage(), // Here I need the value as String
height: Get.height,
fit: BoxFit.cover);
},
))
And this is my _getImage() function
Future<String> _getImage() async {
var url = return await MyStorage().getDownloadUrl(url);
return url;
}
The getDownloadUrl() only returns a String with the download url from Firebase Storage
Future<String> getDownloadUrl(ref) async {
final StorageReference storage = FirebaseStorage().ref().child(ref);
final url = await storage.getDownloadURL();
return url.toString();
}
Because I definitely have to wait for the getDownloadUrl() I have no chance to return the value as String. But I need it as String. Otherwise I get an error.
How would you solve this problem?
A FutureBuilder will build the UI depending on the state of the Future. All you have to do is check if it has the data, then build the image.
Future<String> myFutureString() async {
await Future.delayed(Duration(seconds: 1));
return 'Hello';
}
#override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: myFutureString(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Container(
child: Text(snapshot.data),
);
}
return CircularProgressIndicator();
},
);
}

Future<String> to String into FutureBuilder and Switch statement

in my application i have a simple method as future which that return String as Future:
Future<String> _decodeLessonUrl(BuildContext context) async {
final ContactData data = await Provider.of<ContactDao>(context).getContacts();
final String encryptedUrl =
'encoded string';
final cryptor = new PlatformStringCryptor();
try {
final String decrypted = await cryptor.decrypt(encryptedUrl, '${data.code}');
return decrypted;
} on MacMismatchException {
return null;
}
}
i want to convert this future to simple String into FutureBuilder and Switch statement:
FutureBuilder<PlayLessonResponse>(
future: _myResponse,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
PlayLessonResponse response = snapshot.data;
switch (response.status) {
case -1: // payment is ok. show play view
final Future<String> decodedLink = _decodeLessonUrl(context);// <-- problem is in this line
return PlayerWidget(link:decodedLink);
}
}
}
return Center(
child: CircularProgressIndicator( ),
);
} ),
i get this error:
error: The argument type 'Future<String>' can't be assigned to the parameter type 'String'.
in that i couldn't use then because this method should be return Widget
For converting Future<String> to String you need to use FutureBuilder.
Replace This:
final Future<String> decodedLink = _decodeLessonUrl(context);
return PlayerWidget(link:decodedLink);
With this:
return FutureBuilder<String>(
future: _decodeLessonUrl(context);
builder: (context, snapshot) {
if(snapshot.hasData){
return PlayerWidget(link:snapshot.data);
}else{
return Center(
child: CircularProgressIndicator( ),
);
}
}
);

Get SharedPreferences before widget build

I want to get my userid before the build fires in my statefulwidget.
If I do this, then build will render before I get my id. If I put it in setstate, my build will use the empty string first and then rerenders it again with my id, but this will cause unnecessary behaviours.
So how do I solve this?
String _userid = '';
Future<Null> setUserid() async {
SharedPreferences pref = await SharedPreferences.getInstance();
_userid = pref.getString('FB_USER');
}
initState() {
super.initState();
setUserid();
}
Build
// Widget build
new Flexible(
child: new StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection('users')
.document(_userid)
.collection('rooms')
.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return new Text('Loading...');
return new ListView(
children: snapshot.data.documents
.map(
(DocumentSnapshot document) => new Text('lol'),
// )
//new OverviewPresentation(presentation: document),
)
.toList(),
);
},
),
),
You can use FutureBuilder
Future<String> setUserid() async {
SharedPreferences pref = await SharedPreferences.getInstance();
_userid = pref.getString('FB_USER');
return _userid;
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: setUserid(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return ... // your widget
} else return CircularProgressIndicator();
});
Something like this
You can't, but you can guard against it being null.
Also it's easier to move the StreamBuilder to initState
String _userid = '';
dynamic _data;
Future<Null> setUserid() async {
SharedPreferences pref = await SharedPreferences.getInstance();
_userid = pref.getString('FB_USER');
_data = await Firestore.instance
.collection('users')
.document(_userid)
.collection('rooms')
.snapshots().first;
setState(() {});
}
initState() {
super.initState();
setUserid();
}
return new Flexible(
child:
if(_data == null) return new Text('Loading...');
return new ListView(
children: _data.documents
.map(
(DocumentSnapshot document) => new Text('lol'),
// )
//new OverviewPresentation(presentation: document),
)
.toList(),
);
},
),
),