Flutter- Error with Drawer and Listview.Builder() - flutter

Here's my code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
//import 'package:location/location.dart';
class Home extends StatefulWidget {
Home({Key key, this.title}) : super(key: key);
final String title;
#override
_Home createState() => _Home();
}
class _Home extends State<Home> {
//declared var
String key = "ville";
String txt_ville = "PARIS";
List<String> villes = [];
GlobalKey<ScaffoldState> _drawerKey = GlobalKey();
//Location location;
//LocationData locationData;
//Stream<LocationData> stream;
#override
void initState() {
// TODO: implement initState
getData();
print(villes);
super.initState();
//location = new Location();
//getFirstLocation();
}
/*getFirstLocation() async {
try {
print("Position: ${locationData.latitude}/${locationData.longitude}");
}catch (e) {
print("Error when locating $e");
}
}*/
#override
Widget build(BuildContext context) {
return Scaffold(
key: _drawerKey,
drawer: Drawer(
child: new ListView.builder(
itemCount: villes.length,
itemBuilder: (BuildContext ctx, i) {
return new ListTile(title: new Text(villes[i]));
},
)
),
backgroundColor: Colors.amberAccent,
body: new Column(
children: <Widget>[
Expanded(
flex:4,
child: new Container(
width: double.infinity,
decoration: new BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.only(bottomLeft: Radius.circular(120))
),
child: new Column(
children: <Widget>[
new Padding(
padding: EdgeInsets.only(top: 50),
child: new Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Padding(padding: EdgeInsets.only(left: 20),
child: new IconButton(icon: new Icon(Icons.dehaze, size: 30, color: Colors.black,), onPressed: () {
_drawerKey.currentState.openDrawer();
})),
new Padding(
padding: EdgeInsets.only(left: 30),
child: new Text("TheWeatherApp", style: new TextStyle(
fontSize: 40
),),
),
]),
)
],
),
)
),
Expanded(
flex: 1,
child: new Container(
),
)
],
)
);
}
Future<Null> addTown() async{
return showDialog(barrierDismissible: true, context: context, builder: (BuildContext buildcontext) {
return new SimpleDialog(
contentPadding: EdgeInsets.all(20.0),
title: Text("Add a town"),
children: <Widget>[
new RaisedButton(onPressed: () {
}, child: new Text("Auto locate me"),),
new TextField(
decoration: new InputDecoration(
labelText: "Ville"
),
onSubmitted: (s) {
setData(s);
Navigator.pop(context);
},
)
],
);
});
}
void getData() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
List<String> liste = await sharedPreferences.getStringList(key);
if (liste != null) {
setState(() {
villes = liste;
});
}
}
void setData(String str) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
villes.add(str);
await sharedPreferences.setStringList(key, villes);
getData();
}
void deleteData(String str) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
villes.remove(str);
await sharedPreferences.setStringList(key, villes);
}
}
I'm still a beginner on Flutter and I'm trying to understand why when I launch my application on the emulator and open my drawer with the iconbutton I get this error: Pastbinlink to error
If I deliberately create an error in the code like removing a parenthesis and do a hotreload, and I put the parenthesis back and do a hotreload again then my code works and the listview is displayed...
I have a theory that it's my getData function that initializes the variable villes that doesn't work...
I thank you in advance for any answer!

I can't comment, so I am writing it as an answer.
I haven't tried your code, and I am not sure if it will work, however,
You should try to use FutureBuilder
SharedPreferences.getInstance() is a future that will have value later on, and when you try to build your Drawer, it tries to access a value that doesn't exist.
Instead, if you try it like this. Your drawer will be created once your data is retrieved.
drawer: Drawer(
child: new FutureBuilder<List<String>>(
future: getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.length == 0) {
return Center(child: Text("No data found."));
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext ctx, i) {
return new ListTile(title: new Text(villes[i]));
},
)
}
} else if (snapshot.hasError) {
return checkError();
}
// By default, show a loading spinner.
return Center(child: CircularProgressIndicator());
})
),

Related

Make a list of Flutter Firebase field

Hi, I want to make a list inside the Flutter Firebase field. I'm creating an id for followers in the Field. In Firebase, there is a collection, user ID and followers id in the field. My encodings are as follows. But I'm not making a list. What are the changes I will make?
Followers_card
class FollowersCard extends StatefulWidget {
final snap;
const FollowersCard({
Key? key,
required this.snap,
}) : super(key: key);
#override
State<FollowersCard> createState() => _FollowersCardState();
}
class _FollowersCardState extends State<FollowersCard> {
List<dynamic> followersList = []; // shouldn't use dynamic
getdata() async {
await FirebaseFirestore.instance
.collection("users")
.doc(FirebaseAuth.instance.currentUser!.uid)
.get()
.then((value) async {
// get followerIds
List<String> follwerIds = List.from(value.data()!['followers']);
// loop through all ids and get associated user object by userID/followerID
for (int i = 0; i < follwerIds.length; i++) {
var followerId = follwerIds[i];
var data = await FirebaseFirestore.instance
.collection("users")
.doc(followerId)
.get();
// push that data into followersList variable as we are going
// to use that in listViewBuilder
followersList.add(data);
}
setState(() {});
});
#override
void initState() {
super.initState();
getdata();
}
}
#override
Widget build(BuildContext context) {
// use the listView builder to render the list of followers card
return SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
child: ListView.builder(
shrinkWrap: true,
itemCount: followersList.length,
itemBuilder: (context, index) {
var followerItem = followersList[index];
print('photoUrl');
return _buildFollowersCard(
followerItem['photoUrl'], followerItem['username']);
}),
);
}
Widget _buildFollowersCard(String photoUrl, String username) {
return Container(
height: 70,
width: double.infinity,
color: mobileBackgroundColor,
child: Card(
child: Column(children: [
//Header
Container(
height: 40,
width: double.infinity,
padding: const EdgeInsets.symmetric(
vertical: 4,
horizontal: 16,
).copyWith(right: 0),
child: Row(
children: [
CircleAvatar(
radius: 16,
backgroundImage: NetworkImage(
photoUrl,
),
),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
username,
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
),
),
],
),
)
]),
),
);
}
}
followers_screen
class FollowersScreen extends StatefulWidget {
const FollowersScreen({Key? key}) : super(key: key);
#override
State<FollowersScreen> createState() => _FollowersScreenState();
}
class _FollowersScreenState extends State<FollowersScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: mobileBackgroundColor,
centerTitle: true,
title: Image.asset(
'Resim/logo.png',
height: 50,
),
),
body: StreamBuilder(
stream: FirebaseFirestore.instance.collection('users').snapshots(),
builder: (context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) => FollowersCard(
snap: snapshot.data!.docs[index].data(),
),
);
},
),
);
}
}
The problem is that _FollowersScreenState.initState is in the wrong place. It's inside the function getdata that it is trying to call. The initState is never called. That's why there is no list being built.
Also, setState is the one that assigns State values. So first, populate a temporary list of followers and then assign it to the State one inside the setState callback.
Below is the fixed snippet code for _FollowersScreenState:
class _FollowersCardState extends State<FollowersCard> {
List<dynamic> followersList = []; // shouldn't use dynamic
getdata() async {
List<dynamic> followers = [];
final currentUserSnapshot = await FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.get();
// get followerIds
List<String> follwerIds =
List.from(currentUserSnapshot.data()!['followers']);
// loop through all ids and get associated user object by userID/followerID
for (int i = 0; i < follwerIds.length; i++) {
var followerId = follwerIds[i];
var data = await FirebaseFirestore.instance
.collection('users')
.doc(followerId)
.get();
// push that data into the temp list variable as we are going
// to use that in to setState
followers.add(data);
}
setState(() => followersList = followers);
}
#override
void initState() {
super.initState();
getdata();
}
...

Getting data from a document in FireBase and adding them into a map

I am trying to transfer my data from FireBase Collection to a map. Then, the map data will go into MultiSelectBottomSheetField.
Problems: I am getting "Instance of '_JsonQueryDocumentSnapshot'" instead of 'Home' for example.
I still not getting the list of context from collection into MultiSelectItem
Also, I have noticed that when I start the app, the MultiSelectionItem is empty. It is displaying "Instance of '_JsonQueryDocumentSnapshot'" when I display the main view a second time. I guess that a SetState is probably missing somewhere. But as I can not write set state in a constructor, I am puzzled.
This is driving me nuts as I do not find where the problem is coming from.
Many thanks for your help.
import 'package:flutter_swipe_action_cell/core/cell.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart';
import 'package:multi_select_flutter/bottom_sheet/multi_select_bottom_sheet_field.dart';
import 'package:multi_select_flutter/util/multi_select_item.dart';
String inboxTaskDisplayed='';
int nbRecord=0;
var taskSelectedID;
var taskDone;
//------TEST
class ContextExisting {
final int id;
final String name;
ContextExisting({
this.id,
this.name,
});
}
List<ContextExisting> _contexts = [];
List <ContextExisting>allMyContext=[];
TextEditingController passController = new TextEditingController();
//-----------------
var documentID;
var textController = TextEditingController();
var popUpTextController = TextEditingController();
//_-----------------
class Inbox_Test_For_Chip_Trial extends StatefulWidget {
final String screenSelected;
final String titlePage;
Inbox_Test_For_Chip_Trial(Key key, {#required this.screenSelected, #required this.titlePage,}) : super(key: key);
#override
_Inbox_Test_For_Chip_TrialState createState() => _Inbox_Test_For_Chip_TrialState(screenSelected, titlePage);
}
class _Inbox_Test_For_Chip_TrialState extends State<Inbox_Test_For_Chip_Trial> {
GlobalKey<FormState> _inboxFormKey = GlobalKey<FormState>();
String screenSelected;
String titlePage;
_Inbox_Test_For_Chip_TrialState(this.screenSelected, this.titlePage,);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: new Text(titlePage + ' ('+nbRecord.toString()+')'),
actions: <Widget>[
],
),
backgroundColor: Colors.white,
body: Container(
height: 250,
child: Column(
//mainAxisAlignment: MainAxisAlignment.center,
children: [
//FOR CONTEXT
Flexible(child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('contexts')
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
else {
var docs = snapshot.data.docs;
print('docs');
print (docs[2].id);
print(snapshot.data.docs.length);
int nbContext = snapshot.data.docs.length;
for (int i=0;i<nbContext; i++) {
_contexts.addAll([ContextExisting (id:i, name:snapshot.data.toString())]);
print(_contexts);
}
return Container(
height: MediaQuery.of(context).size.height * .78,
width: MediaQuery.of(context).size.width,
child: ListView(
children: snapshot.data.docs.map((document) {
return Wrap(
children: [Card(
child: SwipeActionCell(
key: ObjectKey(document['context_Name']),
trailingActions: <SwipeAction>[
],
child: ListTile(
trailing: IconButton(
icon: Icon(Icons.keyboard_arrow_right),
onPressed: () async {
taskSelectedID = FirebaseFirestore
.instance
.collection('Users')
.doc(
FirebaseAuth.instance.currentUser
.uid)
.collection('contexts')
.doc(document.id).toString();
}
),
leading: ConstrainedBox(
constraints: BoxConstraints(
minWidth: 30,
minHeight: 35,
maxWidth: 30,
maxHeight: 35,
),
//InkWell(child: Icon(Icons.check_box_outline_blank),
),
title: Text(
document['context_Name'],
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
),
),
]
);
}).toList(),
),
);
}
}),),
Column(children:[
TestWidgetContext(),
]), //MyHomePage())
],
),
),
//bottomNavigationBar: MyBottomAppBar(), //PersistentBottomNavBar(),
);
}
class TestWidgetContext extends StatefulWidget {
TestWidgetContext({Key key}) : super(key: key);
#override
_TestWidgetContextState createState() => _TestWidgetContextState();
}
class _TestWidgetContextState extends State<TestWidgetContext> {
List itemsContext;
List<ContextExisting> _selectedContext5 = [];
final _itemsContext = _contexts
.map((context) => MultiSelectItem<ContextExisting>(context, context.name))
.toList();
#override
void initState() {
_selectedContext5 = _contexts;
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
MultiSelectBottomSheetField(
buttonText: Text("Contexts"),
onConfirm: (val2) {
},
items: _itemsContext,
// initialValue:
// _itemsContext,
),
],
);
}
}
Since the code has a lot of errors, I am writing down my findings.
allMyContext is a list and if you wish to add elements to it you should be wring it as
allMyContext.add(ContextExisting (id:i, name:doc.name));
instead of List<allMyContext> = ContextExisting (id:i, name:doc.name)
Insde the on pressed
taskSelectedID = (document.id).toString();
since you already have taken a document you don't have to query it again
final doc = FirebaseFirestore.instance.collection('users').doc('contexts').get();
and
FirebaseFirestore.instance.collection('Users').doc(FirebaseAuth.instance.currentUser.uid).collection('contexts').snapshots(),
These two are contradictory. Is contexts a collection or a document?
Problem solved.
child: Column(
//mainAxisAlignment: MainAxisAlignment.center,
children: [
//FOR CONTEXT
Flexible(child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser.uid)
.collection('contexts')
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
} else if (snapshot.hasData) {
// if connectionState is waiting
if (snapshot.connectionState == ConnectionState.waiting)
{
return Center(child: CircularProgressIndicator());
} else {
for (int i=0;i<snapshot.data.docs.length;i++){
DocumentSnapshot snap = snapshot.data.docs[i];
_contexts.add(snap['context_Name']);
}
}
}
// return widgets and use data
return Column(children:[
TestWidgetContext(),
]); //MyHomePage())

Flutter Execute Http request on startup using FutureBuilder not working

This is the homepage code of the app I'm currently developing and I have to get all data from API. Therefore I've come up _getLatest that gets the data from the URL which is a list of maps and the data goes into _latest. And I implemented the future builder based on Flutter: Execute a function on Startup - wait for HTTP response parser to extract image URL but things are not quite done.
First of all there are two blue underlines: 1. Future<String> _getLatest() async { says
This function has a return type of 'FutureOr', but doesn't end with a return statement. 2. builder: (BuildContext context, AsyncSnapshot<String> snapshot) { says This function has a return type of 'Widget', but doesn't end with a return statement.
And the main problem is the homepage is that snapshot.connectionState doesn't change to done state so it's loading data eternally. And I'm pretty sure it's because of the code not the URL, the API works fine.
import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:convert';
import 'package:kzstats/common/AppBar.dart';
import 'package:kzstats/common/Drawer.dart';
class Homepage extends StatefulWidget {
#override
_HomepageState createState() => _HomepageState();
}
class _HomepageState extends State<Homepage> {
final String currentPage = 'KZStats';
var _latest = [];
Future<String> _getLatest() async {
var url =
'https://kztimerglobal.com/api/v2.0/records/top/recent?stage=0&tickrate=128&modes_list_string=kz_timer&limit=3';
var httpClient = new HttpClient();
var result;
try {
var request = await httpClient.getUrl(Uri.parse(url));
var response = await request.close();
if (response.statusCode == HttpStatus.ok) {
var json = await response.transform(utf8.decoder).join();
var data = jsonDecode(json);
result = data;
} else {}
} catch (exception) {}
setState(() {
_latest = result;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: HomepageAppBar(currentPage),
drawer: HomepageDrawer(),
body: FutureBuilder<String>(
future: _getLatest(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return new Card(
elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
),
color: Colors.white,
margin: const EdgeInsets.all(20),
child: Text('${_latest[index]}'),
);
},
);
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Loading data from API...'),
)
],
),
);
}
},
),
floatingActionButton: Builder(builder: (builderContext) {
return FloatingActionButton(onPressed: () {
_getLatest();
});
}),
);
}
}
First of all, you have done some wrong things,
if you are using FutureBuilder you can use a snapshot inside it so no need for _latest variable.
you can also use http package for easily requesting data.
inside your _getLatest() function you didn't returned the value and also it was not String.
also you can use Model class for easily assessing data after fetching json.
For your Problem my solution is
import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:convert';
import 'package:http/http.dart' as http;
class Homepage extends StatefulWidget {
#override
_HomepageState createState() => _HomepageState();
}
class _HomepageState extends State<Homepage> {
final String currentPage = 'KZStats';
Future<List<KzTimer>> _getLatest() async {
var url =
'https://kztimerglobal.com/api/v2.0/records/top/recent?stage=0&tickrate=128&modes_list_string=kz_timer&limit=3';
List<KzTimer> result;
try {
var response = await http.get(Uri.parse(url));
if (response.statusCode == HttpStatus.ok) {
result = kzTimerFromJson(response.body);
} else {
print('Something went wrong!');
}
} catch (exception) {}
return result;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<KzTimer>>(
future: _getLatest(),
builder: (BuildContext context, AsyncSnapshot<List<KzTimer>> snapshot) {
return snapshot.hasData ?
ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return new Card(
elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
),
color: Colors.white,
margin: const EdgeInsets.all(20),
child: Text('${snapshot.data[index].playerName}'),
//_latest[index].playerName
);
},
) :
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Loading data from API...'),
)
],
),
) ;
},
),
floatingActionButton: Builder(builder: (builderContext) {
return FloatingActionButton(onPressed: () {
_getLatest();
});
}),
);
}
}
Method to convert the response data to your model class data
List<KzTimer> kzTimerFromJson(String str) => List<KzTimer>.from(json.decode(str).map((x) => KzTimer.fromJson(x)));
Model class
class KzTimer {
KzTimer({
this.id,
this.steamid64,
this.playerName,
this.steamId,
this.serverId,
this.mapId,
this.stage,
this.mode,
this.tickrate,
this.time,
this.teleports,
this.createdOn,
this.updatedOn,
this.updatedBy,
this.place,
this.top100,
this.top100Overall,
this.serverName,
this.mapName,
this.points,
this.recordFilterId,
this.replayId,
});
int id;
String steamid64;
String playerName;
String steamId;
int serverId;
int mapId;
int stage;
String mode;
int tickrate;
double time;
int teleports;
DateTime createdOn;
DateTime updatedOn;
int updatedBy;
int place;
int top100;
int top100Overall;
String serverName;
String mapName;
int points;
int recordFilterId;
int replayId;
factory KzTimer.fromJson(Map<String, dynamic> json) => KzTimer(
id: json["id"],
steamid64: json["steamid64"],
playerName: json["player_name"],
steamId: json["steam_id"],
serverId: json["server_id"],
mapId: json["map_id"],
stage: json["stage"],
mode: json["mode"],
tickrate: json["tickrate"],
time: json["time"].toDouble(),
teleports: json["teleports"],
createdOn: DateTime.parse(json["created_on"]),
updatedOn: DateTime.parse(json["updated_on"]),
updatedBy: json["updated_by"],
place: json["place"],
top100: json["top_100"],
top100Overall: json["top_100_overall"],
serverName: json["server_name"],
mapName: json["map_name"],
points: json["points"],
recordFilterId: json["record_filter_id"],
replayId: json["replay_id"],
);
}
Change return type by: Future<void>. You are returning nothing.
You have an if and an else if but you still need the default case when both conditions are false.
import 'package:flutter/material.dart';
import 'dart:io';
import 'dart:convert';
import 'package:kzstats/common/AppBar.dart';
import 'package:kzstats/common/Drawer.dart';
class Homepage extends StatefulWidget {
#override
_HomepageState createState() => _HomepageState();
}
class _HomepageState extends State<Homepage> {
final String currentPage = 'KZStats';
var _latest = [];
Future<String> _getLatest() async {
var url =
'https://kztimerglobal.com/api/v2.0/records/top/recent?stage=0&tickrate=128&modes_list_string=kz_timer&limit=3';
var httpClient = new HttpClient();
var result;
try {
var request = await httpClient.getUrl(Uri.parse(url));
var response = await request.close();
if (response.statusCode == HttpStatus.ok) {
var json = await response.transform(utf8.decoder).join();
var data = jsonDecode(json);
result = data;
} else {}
} catch (exception) {}
setState(() {
_latest = result;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: HomepageAppBar(currentPage),
drawer: HomepageDrawer(),
body: FutureBuilder<String>(
future: _getLatest(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return new Card(
elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
),
color: Colors.white,
margin: const EdgeInsets.all(20),
child: Text('${_latest[index]}'),
);
},
);
} else if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Loading data from API...'),
)
],
),
);
}
},
),
floatingActionButton: Builder(builder: (builderContext) {
return FloatingActionButton(onPressed: () =>
_getLatest;
}),
);
}
}
The Correct Version of Your Code

How can i separate streambuilder's automatical setstate from my manual setstate in flutter?

I am using a streambuilder for listening changes from firestore database and updating my widget. However my problem is i am using a few manual setstates to make visual changes in my code when a button is pressed and these setstates also triggers streambuilder's setstate and shows an extra loading screen to the users. How can i get seperate setstates? Should i use something else instead of streambuilder?
My code is below:
import 'package:flutter/material.dart';
import 'package:assets_audio_player/assets_audio_player.dart';
import 'numbers.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
Numbers myNumbers = Numbers();
void main(){
runApp(
GameScreen()
);
}
class GameScreen extends StatefulWidget {
static String id ='gamescreen';
#override
_GameScreenState createState() => _GameScreenState();
}
class _GameScreenState extends State<GameScreen> {
bool _initialized = false;
bool _error = false;
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();
getCurrentUser();
}
final _auth =FirebaseAuth.instance;
User loggedInUser;
final _firestore = FirebaseFirestore.instance;
final String collectionPath = 'users';
String docPath;
DocumentReference userdoc;
void getCurrentUser() async{
try{
final user = await _auth.currentUser;
if(user !=null){
loggedInUser =user;
docPath = loggedInUser.uid;
}
}catch(e){
print(e);
}
}
Expanded attachNumber(imagenumber){
return Expanded(
child:FlatButton(
onPressed: (){
setState(() {
if(!myNumbers.numberStatus[1]){
myNumbers.buttonValues['numberimage1'] = imagenumber;
myNumbers.numberStatus[1] =true;
}else if(!myNumbers.numberStatus[2]){
myNumbers.buttonValues['numberimage2'] = imagenumber;
myNumbers.numberStatus[2] =true;
}else if(!myNumbers.numberStatus[3]){
myNumbers.buttonValues['numberimage3'] = imagenumber;
myNumbers.numberStatus[3] =true;
}else if(!myNumbers.numberStatus[4]){
myNumbers.buttonValues['numberimage4'] = imagenumber;
myNumbers.numberStatus[4] =true;
}
});
final assetsAudioPlayer = AssetsAudioPlayer();
assetsAudioPlayer.open(
Audio("assets/audios/click.wav"),
);
},
padding: EdgeInsets.all(0),
child: Image.asset('images/$imagenumber'),
),
);
}
FlatButton showDeleteNumbers(statusNumber,number){
return FlatButton(
onPressed: (){
setState(() {
myNumbers.numberStatus[statusNumber] =false;
myNumbers.buttonValues[number] = 'nonumber.png';
});
},
child: Image.asset('images/'+myNumbers.buttonValues['$number']),
);
}
#override
Widget build(BuildContext context) {
userdoc = _firestore.collection(collectionPath).doc(docPath);
if(_error) {
return Text('error-game', textDirection: TextDirection.ltr);
}
// Show a loader until FlutterFire is initialized
if (!_initialized) {
return Text('Loading', textDirection: TextDirection.ltr);
}
return StreamBuilder<DocumentSnapshot>(
stream: userdoc.snapshots(),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
if(snapshot.hasData){
Map<String, dynamic> userDocument = snapshot.data.data();
print(collectionPath);
print(docPath);
print(snapshot.data);
print(userDocument);
return MaterialApp(
home:Scaffold(
appBar: AppBar(
backgroundColor: Colors.amberAccent,
title: Text('Sayı Avı Oyun Ekranı'),
),
body:Column(
children: <Widget>[
Expanded(
flex: 80,
child: Row(
children: <Widget>[
Expanded(
flex: 50,
child: Column(
children: myNumbers.getUserNumbers(),
),
),
Expanded(
flex: 50,
child: Column(
children: myNumbers.getOpponentNumbers(),
),
),
],
),
),
Expanded(
flex:10,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
showDeleteNumbers(1,'numberimage1'),
showDeleteNumbers(2,'numberimage2'),
showDeleteNumbers(3,'numberimage3'),
showDeleteNumbers(4,'numberimage4'),
],
),
),
Expanded(
flex: 10,
child: Row(
children: <Widget>[
attachNumber('one.png'),
attachNumber('two.png'),
attachNumber('three.png'),
attachNumber('four.png'),
attachNumber('five.png'),
attachNumber('six.png'),
attachNumber('seven.png'),
attachNumber('eight.png'),
attachNumber('nine.png'),
attachNumber('zero.png'),
],
),
),
],
),
),
);
}
},
);
}
}
Try capturing the stream userdoc.snapshots() in a variable in initState instead of calling the snapshots method in every build.
That way you make sure that the streambuilder gets the same stream on every build and thus can maintain its state if you rebuild it

Updating value in nested page(not in widget tree) without refresh when I use provider pattern

I am making an app in the form of a news feed like facebook. I would like to make it user go through contenst in the feed and make them read more detail by clicking on the feed(feedPage to postPage).
I use Firestore as a backend and the provider pattern.
feedPage refers to StreamProvider and myUserData Provider(from main.dart)
Whenever the content is modified or changed or I clicked like, added a comment, it is updated immediately without the need to refresh.
// feed_page.dart
class FeedPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamProvider<List<Post>>.value(
value: firestoreProvider.fetchAllPosts(),
child: Scaffold(
backgroundColor: Colors.grey[200],
appBar: AppBar(
automaticallyImplyLeading: false,
title: GestureDetector(
child: Text('test'),
-----
Container _postActions(
BuildContext context, MyUserData myUserData, Post post) {
return Container(
child: Padding(
padding: const EdgeInsets.fromLTRB(
common_gap, common_gap, common_gap, common_gap),
child: Row(
children: <Widget>[
GestureDetector(
onTap: () {
final route = MaterialPageRoute(
builder: (context) => PostPage(myUserData.data, post));
Navigator.of(context).push(route);
},
the issue is in post_page.dart. When I access to postPage through Navigator, I could see all the details in the postPage. But when I modify something(such as clicking like/heart button, added a comment), I can't see any change in postPage.
// post_page.dart
class PostPage extends StatefulWidget {
final User user;
final Post post;
const PostPage(this.user, this.post, {Key key}) : super(key: key);
#override
_PostPageState createState() => _PostPageState();
}
class _PostPageState extends State<PostPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[200],
appBar: AppBar(
centerTitle: false,
elevation: 0,
actions: <Widget>[
IconButton(
icon: Icon(
Icons.label_outline,
color: Colors.grey[900],
size: 30,
),
onPressed: null,
),
],
),
body: Form(
key: _formkey,
child: SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(
child: _postItem(
context, widget.post.title, widget.post.content),
),
SliverToBoxAdapter(
child: _postComments(),
),
],
),
),
_commentField()
],
),
),
),
);
}
Container _postActions(BuildContext context) {
return Container(
child: Padding(
padding: const EdgeInsets.fromLTRB(
common_gap, common_gap, common_gap, common_xl_gap),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
GestureDetector(
onTap: () {},
child: Row(
children: <Widget>[
Image.asset('assets/comment.png',
width: 17, fit: BoxFit.cover),
SizedBox(
width: common_xxs_gap,
),
Text("${widget.post.numOfComments}", // <-------------------------- my issue is here!
style: TextStyle(fontSize: 13.0, color: Colors.black87))
],
),
),
)
);
}
For example, if I added a comment in this page, widget.post.numOfComments in PostPage should be updated immediately, but to get it updated I got to refresh it by going back to feedPage and return to postPage again.
I think it is because I do not set up provider or setState in postPage.
But I actually have no idea of how to use setState with FirebaseProvider.
And if possible, I want to use Provider either since it is nested in the feedPage(I know it is not in the widget tree of feedPage).
// firestore_provider.dart
class FirestoreProvider with Transformer {
final Firestore _firestore = Firestore.instance;
Future<void> attemptCreateUser({String userKey, String phone}) async {
final DocumentReference userRef =
_firestore.collection(COLLECTION_USERS).document(userKey);
final DocumentSnapshot snapshot = await userRef.get();
return _firestore.runTransaction((Transaction tx) async {
if (!snapshot.exists) {
await tx.set(userRef, User.getMapForCreateUser(userKey, phone));
}
});
}
Stream<User> connectMyUserData(String userKey) {
return _firestore
.collection(COLLECTION_USERS)
.document(userKey)
.snapshots()
.transform(toUser);
}
Stream<List<User>> fetchAllUsers() {
return _firestore
.collection(COLLECTION_USERS)
.snapshots()
.transform(toUsers);
}
Future<Map<String, dynamic>> createNewPost(
String postKey, Map<String, dynamic> postData) async {
final DocumentReference postRef = _firestore
.collection(COLLECTION_POSTS)
.document(postKey); // 없으면 자동으로 Reference 생성됨.
final DocumentSnapshot postSnapshot = await postRef.get();
final DocumentReference userRef =
_firestore.collection(COLLECTION_USERS).document(postData[KEY_USERKEY]);
return _firestore.runTransaction((Transaction tx) async {
if (!postSnapshot.exists) {
await tx.update(userRef, {
KEY_MYPOSTS: FieldValue.arrayUnion([postKey])
});
await tx.set(postRef, postData);
}
});
}
Stream<List<Post>> fetchAllPosts() {
return _firestore
.collection(COLLECTION_POSTS)
.orderBy(KEY_POSTTIME, descending: true)
.snapshots()
.transform(toPosts);
}
Future<Map<String, dynamic>> createNewComments(
String postKey, Map<String, dynamic> commentData) async {
final DocumentReference postRef = _firestore
.collection(COLLECTION_POSTS)
.document(postKey); // 없으면 자동으로 Reference 생성됨.
final DocumentSnapshot postSnapshot = await postRef.get();
print(postSnapshot);
final DocumentReference commentRef =
postRef.collection(COLLECTION_COMMENTS).document();
return _firestore.runTransaction((Transaction tx) async {
if (postSnapshot.exists) {
await tx.set(commentRef, commentData);
int numOfComments = postSnapshot.data[KEY_NUMOFCOMMENTS];
await tx.update(postRef, {KEY_NUMOFCOMMENTS: numOfComments + 1});
}
});
}
Stream<List<CommentModel>> fetchAllComments(String postKey) {
return _firestore
.collection(COLLECTION_POSTS)
.document(postKey)
.collection(COLLECTION_COMMENTS)
.orderBy(KEY_COMMENTTIME)
.snapshots()
.transform(toComments);
}
}
FirestoreProvider firestoreProvider = FirestoreProvider();
plz help.. thanks!