Flutter, how to return different widget based on future value? - flutter

I would like to base on a future bool value, to set different icons pass back to a data card inside a list, I tried .then or FutureBuilder, but still not successful.
Scaffold:
child: ListView.builder(
itemCount: fullList.length,
itemBuilder: (BuildContext context, int index) {
return dataCard(context, fullList, index);
}),
dataCard:
Row(
children: [
Expanded(
flex: 8,
child: Text(dl[i].Name,
style:
TextStyle(color: Colors.blue[400], fontSize: 16)),
),
Expanded(
flex: 1,
child: setFavouriteIcon(dl[i].ID),
),
],
),
setFavouriteIcon:
Widget setFavouriteIcon(_id) {
final marked = markedFavourites(_id).then((value) { //markedFavourites returns Future<bool>
if (value == true) {
return Icon(
size: 24,
Icons.favorite,
color: Colors.red,
);
} else {
return Icon(
size: 24,
Icons.favorite_border_outlined,
color: Colors.red,
);
}
});
return Text(''); //Without this line, Error: A non-null value must be returned
}}

You can include other state as well on FutureBuilder
Widget setFavouriteIcon(_id) {
return FutureBuilder(
future: markedFavourites(_id),// you shouldn't call method directly here on statefulWidget case
builder: (context, snapshot) {
final value = snapshot.hasData && (snapshot.data as bool? ?? false);
if (value == true) {
return Icon(
size: 24,
Icons.favorite,
color: Colors.red,
);
} else {
return Icon(
size: 24,
Icons.favorite_border_outlined,
color: Colors.red,
);
}
},
);
}

you should use FutureBuilder
class FavoriteWidget extends StatelessWidget {
const FavoriteWidget({super.key});
// some future value
Future<bool> markedFavorites() async {
//do smth
return true;
// or return false
}
#override
Widget build(BuildContext context) {
return Center(
child: FutureBuilder<bool>(
future: markedFavorites(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData) {
if (snapshot.data!) {
return const Icon(
Icons.favorite,
color: Colors.red,
);
}
return const Icon(Icons.favorite_border_outlined);
}
},
),
);
}
}

Related

why setState does not change my variable Flutter?

I have a variable to which i assign a value inside gridView.Builder and there is a button, when clicked on which my variable should change, I use setState for this, but it does not change, what could be the reason?
class _CatalogItemsState extends State<CatalogItems> {
Set<int> _isFavLoading = {};
bool isFavorite = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(name!),
),
body: Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Column(
children: [
Expanded(
child: FutureBuilder<List<Product>>(
future: productFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return buildGridShimmer();
} else if (snapshot.hasData) {
final catalog = snapshot.data;
if (catalog!.isEmpty) {
return const Center(
child: Text(
'Нет товаров',
style: TextStyle(
fontSize: 25, fontWeight: FontWeight.bold),
),
);
}
return buildCatalog(catalog);
} else {
print(snapshot.error);
return const Text("No widget to build");
}
}),
),
],
),
),
);
}
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(),
delegate: SliverChildBuilderDelegate(childCount: product.length,
(BuildContext context, int index) {
final media =
product[index].media?.map((e) => e.toJson()).toList();
final photo = media?[0]['links']['local']['thumbnails']['350'];
final productItem = product[index];
isFavorite = productItem.is_favorite; // this is the new value of the variable, work good
IconButton(
icon: Icon(_isFavLoading.contains(index) || isFavorite ? Icons.favorite : Icons.favorite_border, color: Colors.red,),
onPressed: () {
setState(() {
isFavorite = !isFavorite;
});
print('t: ${isFavorite}'); // the value of the variable does not change
},
)
This is because you're not really updating your product object. You must change its value when icon is pressed
onPressed: () {
setState(() {
productItem.is_favorite = !isFavorite;
});
print('t: ${productItem.is_favorite}'); // the value of the variable does not change
}

How to pass Stream data through Navigator in Flutter? (Flutter, Dart, Stream Firebase)

I'm building a chatapp which displays all the available chats as ChatTiles.
A ChatTile shows the name and the last message from the chat using a Stream from Firebase.
By clicking on a ChatTile the Navigator pushes a ConversationScreen Widget.
I would like to somehow pass the stream data along the widget tree to the ConversationScreen. So whenever a message is inserted, the ChatTile shows the last message and the ConversationScreen as well.
But my error is this:
Stream has already been listened to.
Here's a picture:
ChatTiles
ChatTile:
class _ChatRoomTileState extends State<ChatRoomTile> {
Stream<QuerySnapshot> chatRoomStream;
DataFromMessages dataFromMessages;
List<Message> messages;
#override
void initState() {
chatRoomStream =
DB_Service.streamChatRooms(context, widget.dataFromChatRoom.chatRoomId);
getUser();
super.initState();
}
#override
Widget build(BuildContext context) {
if (chatRoomStream == null) return Container();
return StreamProvider<QuerySnapshot>.value(
value: chatRoomStream,
initialData: null,
builder: (context, f) {
QuerySnapshot snapshot = Provider.of<QuerySnapshot>(context);
if (snapshot == null) return Container();
dataFromMessages =
dataFromMessagesFromJson(json.encode(snapshot.docs.first.data()));
messages = dataFromMessages.messages.entries
.map((e) => e.value)
.toList()
.reversed
.toList();
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ConversationScreen(
dataFromChatRoom: widget.dataFromChatRoom,
uid: widget.uid,
chatRoomStream: chatRoomStream,
),
),
);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 0.5),
borderRadius: BorderRadius.circular(8),
),
child: Row(
...
),
),
);
},
);
}
}
The pushed ConversationScreen:
class _ConversationScreenState extends State<ConversationScreen> {
TextEditingController messageController = TextEditingController();
DataFromMessages dataFromMessages;
List<Message> messages;
#override
Widget build(BuildContext context) {
print("Building ConversationScreen");
if (widget.chatRoomStream == null) return Container();
return StreamBuilder(
stream: widget.chatRoomStream.asBroadcastStream(),
initialData: null,
builder: (context, snap) {
//QuerySnapshot snap = Provider.of<QuerySnapshot>(context);
if (snap == null) return Text(" empty");
dataFromMessages = dataFromMessagesFromJson(
json.encode(snap.data.docs.first.data()));
messages = dataFromMessages.messages.entries
.map((e) => e.value)
.toList()
.reversed
.toList();
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
appBar: buildAppBar(context),
body: Column(
children: [
Expanded(
child: messages != null
? ListView.builder(
shrinkWrap: true,
itemCount: messages.length,
itemBuilder: (context, index) {
return ChatTile(
data:
messages[index].data == widget.uid ? 1 : 0,
message: messages[index].message,
timestamp: messages[index].timestamp,
bubbleNip: BubbleNip.no,
);
})
: Container(),
),
TextInput(
uid: widget.uid,
chatRoomId: widget.dataFromChatRoom.chatRoomId,
messages: messages,
),
],
),
bottomNavigationBar: const SizedBox(height: 50),
),
);
});
}
AppBar buildAppBar(BuildContext context) {
return AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Text(
"Chat with Somebody",
style: TextStyle(
color: Theme.of(context).indicatorColor,
),
),
leading: BackButton(color: Theme.of(context).indicatorColor),
);
}
}

The element type 'Future<Widget>' can't be assigned to the list type 'Widget'

I tried couple of solutions given but nothing worked for me
//this basically lays out the structure of the screen
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
alignment: Alignment.center,
padding: const EdgeInsets.only(
top: 30,
bottom: 60,
),
child: Column(
children: [
buildTitle(),
SizedBox(
height: 50,
),
buildForm(),
Spacer(),
buildBottom(),
],
),
),
);
}
//problem is with buildForm
Future<Widget> buildForm() async {
final valid = await usernameCheck(this.username);
return Container(
width: 330,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Form(
key: _userNameformKey,
child: TextFormField(
textAlign: TextAlign.center,
onChanged: (value) {
_userNameformKey.currentState.validate();
},
validator: (value) {
if (value.isEmpty ) {
setState(() {
onNextButtonClick = null;
});
}
else if(!valid){
setState(() {
//user.user.username=value;
onNextButtonClick = null;
showDialog(
context: context,
builder: (context) =>
new AlertDialog(
title: new Text('Status'),
content: Text(
'Username already taken'),
actions: <Widget>[
new ElevatedButton(
onPressed: () {
Navigator.of(context, rootNavigator: true)
.pop(); // dismisses only the dialog and returns nothing
},
child: new Text('OK'),
),
],
),
);
Try using FutureBuilder<T>
Widget build(_) {
return FutureBuilder<bool>(
future: usernameCheck(this.username),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if(!snapshot.hasData) { // not loaded
return const CircularProgressIndicator();
} else if(snapshot.hasError) { // some error
return const ErrorWidget(); // create this class
} else { // loaded
bool valid = snapshot.data;
return Container(/*...details omitted for conciseness...*/);
}
}
)
}

Flutter reset DropdownButton items after select one of them item

in this simple DropdownButton widget when i select one item, items refreshed and select value is first item of SessionsEntity list items, and i can't select another item,selecting them cause of select first item,
I think after selecting item, that cause of rebuild DropdownButton widget
SessionsEntity sessionData;
BarCodesBloc _barcodesBloc;
...
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: ApplicationAppBar(appBarTitle: sessionData!=null? ' (${sessionData.sessionName}) ':'',),
body: BlocListener(
bloc: _barcodesBloc,
listener: (BuildContext context, BarCodesState state) {
if (state is BarCodeScannedSuccessful) {
player.play('ringtones/2.mp3');
}
if (state is BarCodeScannedError) {
}
if (state is BarCodeScannedDuplicate) {
player.play('ringtones/1.mp3');
}
},
child: BlocBuilder(
bloc: _barcodesBloc,
builder: (BuildContext context, BarCodesState state) {
return FutureBuilder(
future: globals.database.sessions.getAllSessionsFuture(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
List<SessionsEntity> sessions = snapshot.data;
List<DropdownMenuItem<SessionsEntity>> _dropdownMenuItems;
if (sessions != null && sessions.length > 0) {
_dropdownMenuItems = buildDropdownMenuItems(sessions);
sessionData = _dropdownMenuItems[0].value;
return Stack(
children: <Widget>[
DropdownButtonHideUnderline(
child: Theme(
data: Theme.of(context).copyWith(
canvasColor: Colors.white,
),
child: DropdownButton(
items: _dropdownMenuItems,
isDense: true,
value:sessionData,
onChanged: onChangeDropdownItem,
isExpanded: true,
hint: Text('please select item',
style: Theme.of(context).textTheme.caption.copyWith(color: Colors.black, )),
),
),
),
],
);
} else {
return Container(
child: Center(
child: Text(
Fa.keywords['noAnySessions'],
style: Theme.of(context).textTheme.caption.copyWith(
color: Colors.black,
),
),
),
);
}
} else {
return Container(
child: Center(
child: Text(
Fa.keywords['noAnySessions'],
style: Theme.of(context).textTheme.caption.copyWith(
color: Colors.black,
),
),
),
);
}
},
);
},
),
),
);
}
onChangeDropdownItem(SessionsEntity selectedCompany) {
setState(() {
sessionData = selectedCompany;
print(sessionData.sessionName);
});
}

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.