Related
When the user enters 1st value and 2nd value and clicks the submit button, StreamBuilder should output the data between the values (age filter). How to do this?
Here is picture of display. and my code - below the picture
Full page code:
class TestScreen extends StatefulWidget {
const TestScreen({super.key});
#override
State<TestScreen> createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
final _firstTextController = TextEditingController();
final _secondTextController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SafeArea(
child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection("users")
// .orderBy("accountCreated")
.where("activeStatus", isEqualTo: "active")
// .where("uid", isNotEqualTo: FirebaseAuth.instance.currentUser!.uid)
.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
if (streamSnapshot.connectionState == ConnectionState.waiting ||
!streamSnapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
itemCount: streamSnapshot.data!.docs.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _firstTextController,
decoration: const InputDecoration(
hintText: "from",
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _secondTextController,
decoration: const InputDecoration(
hintText: "to",
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextButton(
onPressed: () {
print("First value: ${_firstTextController.text}");
print(
"Second value: ${_secondTextController.text}");
},
child: const Text("Submit"),
),
),
],
);
} else {
return Text(
streamSnapshot.data!.docs[index - 1]["displayName"]);
}
},
);
},
),
),
);
}
}
class TestScreen extends StatefulWidget {
const TestScreen({super.key});
#override
State<TestScreen> createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
int userLowerValue = 20;
int userUpperValue = 60;
Stream<QuerySnapshot> getDataStream(int lower, int upper) {
return FirebaseFirestore.instance
.collection("users")
.where("age", isGreaterThan: lower)
.where("age", isLessThan: upper)
.snapshots();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
onPressed: () {
int lower = 20;
int upper = 60;
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Enter Filter Values"),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.number,
decoration:
InputDecoration(labelText: "Lower Value"),
onChanged: (value) {
lower = int.parse(value);
},
),
TextFormField(
keyboardType: TextInputType.number,
decoration:
InputDecoration(labelText: "Upper Value"),
onChanged: (value) {
upper = int.parse(value);
},
),
],
),
),
actions: <Widget>[
TextButton(
child: Text("Apply"),
onPressed: () {
Navigator.of(context).pop();
setState(() {
userLowerValue = lower;
userUpperValue = upper;
});
},
),
],
);
},
);
},
icon: const Icon(Icons.filter))
],
),
body: SafeArea(
child: StreamBuilder(
stream: getDataStream(userLowerValue, userUpperValue),
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
if (streamSnapshot.connectionState == ConnectionState.waiting ||!streamSnapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (streamSnapshot.data!.docs.isEmpty) {
return const Center(
child: CustomText(
text: "No results found",
size: 30,
),
);
}
return ListView.builder(
itemCount: streamSnapshot.data!.docs.length,
itemBuilder: (context, index) {
return Text(streamSnapshot.data!.docs[index]["displayName"]);
},
);
},
),
),
);
}
}
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),
);
}
}
I actually have a searchBar(autocomplete) that is working.
When i select a result, the displaySnack() is working, it displays a snackBar, but i would like to display the content of testList().
My goal is to understand how I can launch another widget, to be able to add new widget on the page again and again.
My final goal is once i have the selected value, to make an http request, get a list as return and display a listview.
The function is executed ( i can see it in debugger ) but doesn't display anything..
(i'm new to flutter, so please explain your response if possible :) )
onSuggestionSelected : yes i know that it is void..
import 'package:drawer/src/share/snack_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import '../models/post_model.dart';
import '../services/http_service.dart';
// import 'package:http/http.dart' as http;
class PostsPage extends StatelessWidget {
final String title;
const PostsPage({Key? key, required this.title}) : super(key: key);
static Future<List<Post>> filterList(String value) async {
List<Post> list = await HttpService.fetchPosts();
return list.where(
(x) => x.title.toLowerCase().contains(value.toLowerCase())).toList();
}
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(title),
),
body: SafeArea(
child: Container(
padding: EdgeInsets.all(16),
child: TypeAheadField<Post?>(
debounceDuration: Duration(milliseconds: 500),
hideSuggestionsOnKeyboardHide: false,
textFieldConfiguration: TextFieldConfiguration(
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
hintText: 'Select the namespace...',
),
),
suggestionsCallback: filterList,
itemBuilder: (context, Post? suggestion) {
final user = suggestion!;
return ListTile(
title: Text(user.title),
);
},
noItemsFoundBuilder: (context) => Container(
height: 100,
child: Center(
child: Text(
'No Namespace Found.',
style: TextStyle(fontSize: 24),
),
),
),
onSuggestionSelected: (Post? suggestion) {
final user = suggestion!;
displaySnack(context, ' Namespace: '+user.title);
testList(context); ################################ HERE
},
),
),
),
);
}
Widget testList(BuildContext context) {
return ListView.separated(
separatorBuilder: (BuildContext context, int index) => const Divider(),
itemCount: 2,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text("ppp"),
subtitle: Text("ppp"),
leading: CircleAvatar(
backgroundImage: NetworkImage(
"https://images.unsplash.com/photo-1547721064-da6cfb341d50"))
));
});
}
I need that : https://prnt.sc/136njev
It is obvious that you want the widget to rebuild to show the result. The most straightforward method is to use StatefulWidget. So I use it in your case(You can also find lots of ways to manage the state List of state management approaches)
Change your PostsPage to a StatefulWidget and rebuild when the user is selected
Add a Column in your PostsPage and separate into 2 parts: TypeAheadField & Result
Result part can use FutureBuilder (which can show loading indicator when data is not ready)
PostsPage:
class PostsPage extends StatefulWidget {
final String title;
const PostsPage({Key? key, required this.title}) : super(key: key);
static Future<List<Post>> filterList(String value) async {
// skip
}
#override
_PostsPageState createState() => _PostsPageState();
}
class _PostsPageState extends State<PostsPage> {
Post? user;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SafeArea(
child: Column(
children: [
Container(
padding: EdgeInsets.all(16),
child: TypeAheadField<Post>(
// ...
// skip
// ...
onSuggestionSelected: (Post? suggestion) {
setState(() {
user = suggestion!;
displaySnack(context, ' Namespace: '+user.title);
});
},
),
),
Expanded(child: MyResult(post: user)),
],
),
),
);
}
}
Result part:
(I make it an isolated StatelessWidget just for better reading. You can use the original method to build the widget)
class MyResult extends StatelessWidget {
const MyResult({
required this.post,
Key? key,
}) : super(key: key);
final Post? post;
Future<List<OtherObject>> getOtherObjects(Post? post) async{
if(post == null){
return [];
}else{
return Future.delayed(Duration(seconds:3),()=>[OtherObject(title: '001'),OtherObject(title: '002'),OtherObject(title: '003')]);
}
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<OtherObject>>(
future: getOtherObjects(post),
builder: (context, snapshot) {
if(snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
final result = snapshot.data!;
return ListView.separated(
separatorBuilder: (BuildContext context,
int index) => const Divider(),
itemCount: result.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(result[index].title),
subtitle: Text("ppp"),
leading: CircleAvatar(
backgroundImage: NetworkImage(
"https://images.unsplash.com/photo-1547721064-da6cfb341d50"),
),
),
);
},
);
}else {
return Center(child: CircularProgressIndicator());
}
}
);
}
}
So what you are doing is basically just creating a ListView with your testList() function call and doing nothing with it, but what you want to do is to have that widget show up on the screen, right?
Flutter doesn't just show Widget if you create a new one, you must tell it to render. Just imagine you are doing preparing Widgets (e.g. Widgets in Widgets) and Flutter would render it immediately to the screen without you being finished, that wouldn't be that great.
You need to push that Widget over the Navigator widget that Flutter provides you.
onSuggestionSelected: (Post? suggestion) {
final user = suggestion!;
displaySnack(context, ' Namespace: '+user.title);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => testList(context)),
);
}
I suggest you to read this article to Navigation Basics.
you can use listView builder to show the selected results.
onSuggestionSelected: (Post? suggestion) {
final user = suggestion!;
displaySnack(context, ' Namespace: '+user.title);
//get results
var results = fetchResult(suggestion);
//return a listview of the results
return ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: results.length,
itemBuilder: (_context, index) {
Post post = results[index];
return Card(
elevation: 2,
child: InkWell(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
border: Border.all(color: Colors.black),
),
child: DefaultTextStyle(
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
color: Colors.white),
child: Row(children: [
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(post.data),
],
),
),
]),
),
),
onTap: () {
//do something when user clicks on a result
},
));
}
},
If you want to show a list of selected items then you will have to add a ListView in the widget tree. Also use a StatefullWidget instead of StatelessWidget, because whenever you select an item, the selected list gets changed thus state.
sample code for state
List<Post> selectedPosts;
#override
void initState() {
super.initState();
selectedPosts = [];
}
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(title),
),
body: SafeArea(
child: Column(
children: [
Container(
padding: EdgeInsets.all(16),
child: TypeAheadField<Post?>(
debounceDuration: Duration(milliseconds: 500),
hideSuggestionsOnKeyboardHide: false,
textFieldConfiguration: TextFieldConfiguration(
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
hintText: 'Select the namespace...',
),
),
suggestionsCallback: filterList,
itemBuilder: (context, Post? suggestion) {
final user = suggestion!;
return ListTile(
title: Text(user.title),
);
},
noItemsFoundBuilder: (context) => Container(
height: 100,
child: Center(
child: Text(
'No Namespace Found.',
style: TextStyle(fontSize: 24),
),
),
),
onSuggestionSelected: (Post? suggestion) {
final user = suggestion!;
displaySnack(context, ' Namespace: '+user.title);
setState(()=> selectedPosts.add(suggestion));
},
),
),
testList(context),
],
),
),
);
and in the testList function change the itemcount
itemCount: 2,
to
itemCount: selectedPosts?.length ?? 0,
I am trying to show some information brought from firebase in web flutter but I have problems implementing FutureBuilder, since it does not show the information, the widget is not rendering anything the page is completely blank, attached I send the complete code of the page that is presenting the problem since I have followed all the tutorials that I have found but I can not make it show the data, in the same way I also attach the Json that returns the complement that I am using to obtain the data from firebase, I hope for the collaboration, thank you very much
Complete Code
class DriverPages extends StatefulWidget {
static final routeName = 'DriverPage';
DriverPages({Key key}) : super(key: key);
#override
_DriverPagesState createState() => _DriverPagesState();
}
DatabaseRef driverRef =
FirebaseDatabaseWeb.instance.reference().child('drivers');
class _DriverPagesState extends State<DriverPages> {
GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
List prueba;
Future<List> driverItem() async {
DatabaseSnapshot snapshot = await driverRef.once();
Map<String, dynamic> json = snapshot.value;
return json.values.toList();
}
Widget driverList() {
return FutureBuilder<List>(
future: driverItem(),
builder: (contetx, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
final value = snapshot.data[index];
return ListTile(
title: Text(value['fullname']),
subtitle: Text(value['email']),
);
},
);
}
},
);
}
#override
Widget build(BuildContext context) {
if (userSnapshot != null) {
return Scaffold(
key: scaffoldKey,
drawer: Container(
width: 320,
color: Colors.white,
child: Drawer(
child: PatimovilMenu(),
),
),
body: Container(
width: double.infinity,
height: double.infinity,
padding: EdgeInsets.symmetric(
vertical: 25,
horizontal: 30,
),
child: Column(
children: <Widget>[
PatimovilHeader(
onPressed: () {
scaffoldKey.currentState.openDrawer();
},
titlePage: 'Driver Manager',
),
Container(
width: MediaQuery.of(context).size.width,
height: (MediaQuery.of(context).size.height) - 260,
alignment: Alignment.center,
child: Center(
child: driverList(),
),
),
PatimovilFooder(),
],
),
),
);
} else {
Navigator.pushNamedAndRemoveUntil(
context,
LoginPage.routeName,
(route) => false,
);
}
}
}
This is the Json that returns the dependency I am using
{VOVQv28SU2c0GgXJKcSp8UFchPz2:
{email: bibiana206#gmail.com, fullname: bibiana, identification: e8104970, phone: 2095770, status: Waiting,
vehicle_details:
{car_brand: Ferrari, car_color: rojo, car_model: cretta, car_plate: az12}
},
dBncphEFOZbzGEt5qn44sD9BYVK2:
{email: bolivia20192019#gmail.com, fullname: Gustavo Barrios, identification: 14326048, phone: 60697350, status: Active,
vehicle_details:
{car_brand: Renault, car_color: Gris, car_model: Duster, car_plate: AY6787,}
}
}
I can't get the data to show me, it doesn't render anything
You should check the current state of the future as there are four connection states:
ConnectionState.none
ConnectionState.waiting
ConnectionState.active
ConnectionState.done
You were returning a widget only when snapshot.hasData which will occur only when the connection state is done & there are no errors.
In all other cases, your method driverList will be returning nothing.
So, update your method as follows:
Widget driverList() {
return FutureBuilder<List>(
future: driverItem(),
builder: (contetx, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Center(child: Text('Something went wrong'));
}
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
final value = snapshot.data[index];
return ListTile(
title: Text(value['fullname']),
subtitle: Text(value['email']),
);
},
);
}
}
return Center(child: CircularProgressIndicator());
},
);
}
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.