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

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),
);
}
}

Related

Filter StreamBuilder snapshot data - flutter

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"]);
},
);
},
),
),
);
}
}

Draggable list view flutter

Hi the listview that is displayed below allows you to scroll a series of elements that are shown in a flutter list, the problem is that it is not possible to scroll the entire list, how can I correct this thing?
Flutter code:
import 'package:costal/Model/Maintenance.dart';
import 'package:costal/View/constants/color.dart';
import 'package:costal/View/utils/support.dart';
import 'package:costal/View/widgets/SceduledCard.dart';
import 'package:flutter/material.dart';
import 'package:rflutter_alert/rflutter_alert.dart';
import 'DetailScreen.dart';
class ScheduledScreen extends StatelessWidget {
const ScheduledScreen({Key? key}) : super(key: key);
Future<List<Maintenance>> loadvalues() async {
return await Maintenance.getMaintenanceScheduled();
}
Future<bool> completeDay(BuildContext context, Maintenance man) async {
var check = await man.completeDay();
if (check == true) {
Share.alertCustom(context, AlertType.success, 'Manutenzione Completa', 'Hai completato la manutenzione', true);
} else {
Share.alertCustom(context, AlertType.error, 'Errore', 'non è stato possibile completare le manutenzioni', false);
}
return check;
}
Widget getLoader() {
return const Align(
alignment: Alignment.center,
child: CircularProgressIndicator(
value: 50,
semanticsLabel: 'Loading value',
),
);
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Maintenance>>(
future: loadvalues(),
builder: (BuildContext context, AsyncSnapshot<List<Maintenance>> snapshot) {
if (!snapshot.hasData) {
return getLoader();
} else {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text(
'Manutenzioni',
style: TextStyle(
fontFamily: 'Poppins',
color: kPrimaryColor,
),
),
leading: CircleAvatar(
radius: 20,
backgroundColor: const Color(0xff94d500),
child: IconButton(
icon: const Icon(Icons.access_alarms_sharp),
onPressed: () async {
completeDay(context, snapshot.data![0]);
},
),
),
),
body: Wrap(
//the magic
children: [
Container(
padding: const EdgeInsets.all(20),
child: ListView.separated(
shrinkWrap: true,
itemCount: snapshot.data!.length,
separatorBuilder: (context, index) {
return const SizedBox(
height: 10,
);
},
itemBuilder: (context, index) {
return GestureDetector(
onTap: (() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailScreen(snapshot.data![index])),
);
}),
child: SceduledCard(man: snapshot.data![index], c: Colors.blue),
);
}))
],
),
);
}
});
}
}

Flutter Sqflite Toggling between Screens based on Login Status creates null operator used on null value error

I am trying to toggle between Login Screen and HomeScreen based on the user status. The logic seems to be working as long as I don't put HomeScreen.
I replaced HomeScreen with a different screen to check and the app works as it should. It displays different screens on hot restart based on the user's login status. But as soon as I try to put HomeScreen I get null operator used on null value error.
Here is the toggle logic.
class Testing extends StatefulWidget {
const Testing({super.key});
#override
State<Testing> createState() => _TestingState();
}
class _TestingState extends State<Testing> {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: TodoServiceHelper().checkifLoggedIn(),
builder: ((context, snapshot) {
if (!snapshot.hasData) {
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
}
if (snapshot.hasError) {
print(snapshot.hasError);
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
}
if (snapshot.data!.isNotEmpty) {
print(snapshot.data);
return RegisterPage();
// returning HomePage gives null check operator used on null value error
} else
return Login();
}),
);
}
}
Here is the HomeScreen
class HomePage extends StatefulWidget {
String? username;
HomePage({this.username});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final GlobalKey<FormState> formKey = GlobalKey();
TextEditingController termController = TextEditingController();
void clearText() {
termController.clear();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
onPressed: () {
User loginUser =
User(username: widget.username.toString(), isLoggedIn: false);
TodoServiceHelper().updateUserName(loginUser);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (BuildContext context) => Login()));
},
icon: Icon(Icons.logout),
color: Colors.white,
)
],
title: FutureBuilder(
future: TodoServiceHelper().getTheUser(widget.username!),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
}
return Text(
'Welcome ${snapshot.data!.username}',
style: TextStyle(color: Colors.white),
);
}),
),
body: SingleChildScrollView(
child: Column(children: [
Column(
children: [
Padding(
padding: const EdgeInsets.all(12.0),
child: Form(
key: formKey,
child: Column(
children: <Widget>[
TextFormField(
controller: termController,
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
enabledBorder: OutlineInputBorder(),
labelText: 'search todos',
),
),
TextButton(
onPressed: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ShowingSerachedTitle(
userNamee: widget.username!,
searchTerm: termController.text,
)),
);
print(termController.text);
clearText();
setState(() {});
},
child: Text(
'Search',
)),
Divider(
thickness: 3,
),
],
),
),
),
],
),
Container(
child: Stack(children: [
Positioned(
bottom: 0,
child: Text(
' done Todos',
style: TextStyle(fontSize: 12),
),
),
IconButton(
onPressed: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
CheckingStuff(userNamee: widget.username!)),
);
setState(() {});
},
icon: Icon(Icons.filter),
),
]),
),
Divider(
thickness: 3,
),
Container(
child: TodoListWidget(name: widget.username!),
height: 1000,
width: 380,
)
]),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Color.fromARGB(255, 255, 132, 0),
onPressed: () async {
await showDialog(
barrierDismissible: false,
context: context,
builder: ((context) {
return AddNewTodoDialogue(name: widget.username!);
}),
);
setState(() {});
},
child: Icon(Icons.add),
),
);
}
}
The function used to return user with loginStatus true
Future<List<User>> checkifLoggedIn() async {
final Database db = await initializeDB();
final List<Map<String, Object?>> result = await db.query(
'users',
where: 'isLoggedIn = ?',
whereArgs: ['1'],
);
List<User> filtered = [];
for (var item in result) {
filtered.add(User.fromMap(item));
}
return filtered;
}
the problem is here
you used ! sign on a nullable String , and this string is nullable,
try to use this operation (??) so make it
widget.username??"" by this line you will check if the user name is null it will be replaced by an empty string.

Flutter - Returning to previous page from AppBar is not refreshing the page, with Navigator.pop(context)

I was trying to get the list page refreshed if a method was run on another page. I do pass the context using the push navigation.
I tried to follow these 3 answers Answer 1 Answer 2 and Answer 3 and I am not able to manage the states here.
This is the first list page which needs to be refreshed. It calls a class
class _PageLocalState extends State<PageLocal> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: SafeArea(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: widget.allLocal.length,
//padding: const EdgeInsets.only(top: 10.0),
itemBuilder: (context, index) {
return LocalCard(widget.allLocal[index]);
},
)),
)
],
),
);
}
}
The next class:
class LocalCardState extends State<LocalCard> {
FavData localdet;
LocalCardState(this.localdet);
ListTile makeListTile() => ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
title: Text(
localdet.name,
style: TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(localdet.loc),
trailing: Icon(Icons.keyboard_arrow_right, size: 30.0),
onTap: () => navigateToDetail(localdet),
);
Widget get localCard {
return new Card(
elevation: 4.0,
margin: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
child: Container(
child: makeListTile(),
));
}
#override
Widget build(BuildContext context) {
return new Container(
child: localCard,
);
}
navigateToDetail(FavData localdet) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FavouriteDetailPage(
mndet: localdet,
)));
setState(() {});
}
}
Now this is routing to the final detail page:
class _FavouriteDetailPageState extends State<FavouriteDetailPage> {
bool isFav = false;
FavData mndet;
_FavouriteDetailPageState(this.mndet);
// reference to our single class that manages the database
final dbHelper = DatabaseHelper.instance;
#override
Widget build(BuildContext context) {
Widget heading = new Container(...);
Widget middleSection = new Expanded(...);
Widget bottomBanner = new Container(...);
Widget body = new Column(...);
final makeBottom = Container(
height: 55.0,
child: BottomAppBar(
color: Color.fromRGBO(36, 36, 36, 1.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new FavIconWidget(mndet),
],
),
),
);
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('The Details'),
backgroundColor: Color.fromRGBO(36, 36, 36, 1.0),
),
body: Container(
child: Card(
elevation: 5.0,
shape: RoundedRectangleBorder(
side: BorderSide(color: Colors.white70, width: 1),
borderRadius: BorderRadius.circular(10),
),
margin: EdgeInsets.all(20.0),
child: Padding(
padding: new EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
child: body,
),
),
),
bottomNavigationBar: makeBottom,
);
}
void share(BuildContext context, FavData mndet) {
final RenderBox box = context.findRenderObject();
final String shareText = "${mndet.name} - ${mndet.desc}";
Share.share(shareText,
subject: mndet.loc,
sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size);
}
}
class FavIconWidget extends StatefulWidget {
final FavData mnforIcon;
FavIconWidget(this.mnforIcon);
#override
_FavIconWidgetState createState() => _FavIconWidgetState();
}
class _FavIconWidgetState extends State<FavIconWidget> {
final dbHelper = DatabaseHelper.instance;
Future<bool> get isFav async {
final rowsPresent = await dbHelper.queryForFav(widget.mnforIcon.id);
if (rowsPresent > 0) {
print('Card Loaded - Its Favourite already');
return false;
} else {
print('Card Loaded - It is not favourite yet');
return true;
}
}
void _insert() async {...}
void _delete() async {...}
#override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
future: isFav,
initialData:
false, // you can define an initial value while the db returns the real value
builder: (context, snapshot) {
if (snapshot.hasError)
return const Icon(Icons.error,
color: Colors.red); //just in case the db return an error
if (snapshot.hasData)
return IconButton(
icon: snapshot.data
? const Icon(Icons.favorite_border, color: Colors.white)
: Icon(Icons.favorite, color: Colors.red),
onPressed: () => setState(() {
if (!snapshot.data) {
print('Its favourite so deleting it.');
_delete();
} else {
print('Wasnt fav in the first place so inserting.');
_insert();
}
}));
return CircularProgressIndicator(); //if there is no initial value and the future is not yet complete
});
}
}
I am sure this is just some silly coding I have done but just not able to find out. Where.
I tried adding Navigator.pop(context); in different sections of the detail page and it fails.
Currently, I have to navigate back to the Favourites list page and then HomePage and then back to Favourites ListPage to refresh the list.
try this.. Anywhere you are using Navigator.pop or Navigator.push .. Instead of this use this:
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (BuildContext context) => Password())
);
//instead of Password use the name of the page(the second page you want to go to)

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.