Snackbar appearing repeatedly after appearing once - flutter

I have shown snackbar after incorrect login in flutter. But after appearing once, the snackbar is appearing repeatedly without even pressing the submit button. Where am I doing wrong? Please help. Here is my code below. In the snapshot.data.status == '2' portion I have showing the snackbar. I want the snackbar to be displayed only when the login falis i.e. when snapshot.data.status == '2' satisfies.
class MyApp extends StatefulWidget {
final UserDataStorage storage;
MyApp({Key key, #required this.storage}) : super(key: key);
#override
State<StatefulWidget> createState() {
return _TextControl();
}
}
class _TextControl extends State<MyApp> {
final _formKey = GlobalKey<FormState>();
bool snackBarActive = false;
#override
void initState() {
super.initState();
widget.storage.readCounter().then((int value) {
if (value > 0) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TextInput(storage: GetDataStorage())),
);
}
});
}
final TextEditingController _controller = TextEditingController();
final TextEditingController _controller2 = TextEditingController();
#override
void dispose() {
_controller.dispose();
_controller2.dispose();
super.dispose();
}
Future<Login> _futureLogin;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
backgroundColor: Colors.purple[400],
title: Text('Login'),
),
body: Container(
padding: EdgeInsets.only(left: 15, top: 20, right: 15, bottom: 20),
child: Column(
children: <Widget>[
Center(
child: Image(image: AssetImage('assets/images/logo.png')),
),
Container(
padding: EdgeInsets.only(left: 0, top: 20, right: 0, bottom: 0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Column(
children: <Widget>[
TextFormField(
controller: _controller,
decoration: InputDecoration(hintText: 'Email'),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value.isEmpty || !value.isValidEmail()) {
return 'Please enter valid email.';
}
return null;
},
),
TextFormField(
controller: _controller2,
decoration: InputDecoration(hintText: 'Password'),
obscureText: true,
validator: (value) {
if (value.isEmpty) {
return 'Please enter password.';
}
return null;
},
),
Container(
margin: EdgeInsets.only(top: 10.0),
child: RaisedButton(
color: Colors.purple[400],
padding: EdgeInsets.only(
left: 130, top: 10, right: 130, bottom: 10),
textColor: Colors.white,
onPressed: () {
if (_formKey.currentState.validate()) {
setState(() {
_futureLogin = userLogin(
_controller.text, _controller2.text);
});
Scaffold.of(context)
.removeCurrentSnackBar();
}
},
child: Text('Login',
style: TextStyle(fontSize: 18.0)),
),
),
],
),
(_futureLogin != null)
? FutureBuilder<Login>(
future: _futureLogin,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.status == '1') {
widget.storage
.writeCounter(snapshot.data.usrid);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TextInput(
storage: GetDataStorage())),
);
} else if (snapshot.data.status == '2') {
snackBarActive = true;
if (snackBarActive) {
Scaffold.of(context)
.showSnackBar(SnackBar(
content: Text(
"Invalid login credentials.",
style: TextStyle(
color: Colors.red[100])),
))
.closed
.whenComplete(() {
setState(() {
snackBarActive = false;
});
});
}
return Text('');
}
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return Center(
child: CircularProgressIndicator());
},
)
: Text(''),
],
)),
),
],
)),
);
}
}

Have you tried changing the status variable to 1 after checking snapshot.data.status == '2', try that and it might help you stop the continuous snackbar widget showing up.

Write this code before calling snackbar,
ScaffoldMessenger.of(context).hideCurrentSnackBar();
example;
getSnackBar(BuildContext context, String snackText) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(snackText)));
}

Related

Device calendar accounts is not display in flutter

Calendars from my device and local accounts are not displayed on my screen The calendar is correctly presented when I execute my code using USB, however when I generate an apk and then install it on my device, the calendar is not visible.i am using device calendar plugin i get this code from there account Can anyone assist in identifying the problem?
class CalendarsPage extends StatefulWidget {
const CalendarsPage(
{Key? key,
required String calendarName,
required void Function() opendrawer,
required double animationtime})
: super(key: key);
#override
_CalendarsPageState createState() {
return _DoctorCalendarsPageState();
}
}
class _CalendarsPageState extends State<CalendarsPage> {
late CalendarPlugin _CalendarPlugin;
List<Calendar> _calendar = [];
List<Calendar> get _writableCalendars =>
_calendar.where((c) => c.isReadOnly == false).toList();
List<Calendar> get _readOnlyCalendars =>
_calendar.where((c) => c.isReadOnly == true).toList();
_CalendarsPageState() {
_deviceCalendarPlugin = DeviceCalendarPlugin();
}
#override
void initState() {
super.initState();
_retrieveCalendars();
_CalendarsPageState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
flex: 1,
child: ListView.builder(
itemCount: _calendar.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
key: Key(_calendar[index].isReadOnly == true
? 'readOnlyCalendar${_readOnlyCalendars.indexWhere((c) => c.id == _calendar[index].id)}'
: 'writableCalendar${_writableCalendars.indexWhere((c) => c.id == _calendar[index].id)}'),
onTap: () async {
await Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return DoctorEventsPage(_calendar[index],
key: const Key('calendarEventsPage'));
}));
},
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Container(
height: 22,
child: Row(
children: [
Expanded(
flex: 1,
child: Text(
_calendar[index].name!,
style: Theme.of(context).textTheme.subtitle1,
),
),
Container(
width: 15,
height: 15,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(_calendar[index].color!)),
),
const SizedBox(width: 10),
// Container(
// margin: const EdgeInsets.fromLTRB(0, 0, 5.0, 0),
// padding: const EdgeInsets.all(3.0),
// decoration: BoxDecoration(
// border: Border.all(color: Colors.blueAccent)),
// // child: const Text('Default'),
// ),
Icon(_calendar[index].isReadOnly == true
? Icons.lock
: Icons.lock_open)
],
),
),
),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
final createCalendar = await Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return DoctorCalendarAddPage();
}));
if (createCalendar == true) {
_retrieveCalendars();
}
},
child: const Icon(Icons.add),
backgroundColor: Colors.green,
),
);
}
void _retrieveCalendars() async {
try {
var permissionsGranted = await _deviceCalendarPlugin.hasPermissions();
if (permissionsGranted.isSuccess &&
(permissionsGranted.data == null ||
permissionsGranted.data == false)) {
permissionsGranted = await _deviceCalendarPlugin.requestPermissions();
if (!permissionsGranted.isSuccess ||
permissionsGranted.data == null ||
permissionsGranted.data == false) {
return;
}
}
final calendarsResult = await _deviceCalendarPlugin.retrieveCalendars();
setState(() {
_calendar = calendarsResult.data as List<Calendar>;
});
} on PlatformException catch (e) {
if (kDebugMode) {
print(e);
}
}
}
}

How to return the PageView to its initial state in Flutter

I am making a quiz app and at first everything works fine, but when I do a quiz the first time, it does the correct or incorrect answer check perfectly.
But when I go back to quiz without restarting the app just navigating from one page to another the PageView does not reset its state again.
Before taking the quiz
enter image description here
After I do the quiz and I want to do it again without restart the app, I get the checked answers.
enter image description here
How to return the PageView to its initial state without restart the app
Here is my code:
import 'package:flutter/material.dart';
import 'package:quizapp/src/models/quiz_model.dart';
import 'package:quizapp/src/screens/result_screen.dart';
class QuizScreen extends StatefulWidget {
const QuizScreen({Key? key}) : super(key: key);
#override
State<QuizScreen> createState() => _QuizScreenState();
}
class _QuizScreenState extends State<QuizScreen> {
int _questionNumber = 1;
late PageController _controller;
int _score = 0;
#override
void initState() {
_controller = PageController(initialPage: 0);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: PageView.builder(
physics: const NeverScrollableScrollPhysics(),
controller: _controller,
itemCount: questions.length,
itemBuilder: (context, index) {
final _question = questions[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 16,
),
Text(
_question.text,
style: const TextStyle(fontSize: 22),
),
const SizedBox(
height: 16,
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: _question.options
.map((option) => GestureDetector(
onTap: () {
Future.delayed(
const Duration(milliseconds: 250),
() {
if (_questionNumber <
questions.length) {
_controller.nextPage(
duration: const Duration(
milliseconds: 250),
curve: Curves.easeInExpo);
setState(() {
if (option.isCorrect == true) {
_score++;
}
});
setState(() {
_questionNumber++;
// _isLocked = false;
});
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) =>
ResultScreen(
score: _score),
));
}
});
if (_question.isLocked) {
return;
} else {
setState(() {
_question.isLocked = true;
_question.selectedOption = option;
});
}
},
child: Container(
height: 50,
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.symmetric(
vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFF6949FD),
borderRadius:
BorderRadius.circular(16),
border: Border.all(
color: getColorForOption(
option, _question))),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
option.text,
style: const TextStyle(
fontSize: 18,
color: Colors.white),
),
const SizedBox(width: 10),
getIconForOption(option, _question)
],
),
),
))
.toList(),
)))
]);
},
)),
),
],
),
));
}
Color getColorForOption(Option option, Question _question) {
final isSelected = option == _question.selectedOption;
if (_question.isLocked) {
if (isSelected) {
return option.isCorrect ? Colors.green : Colors.red;
} else if (option.isCorrect) {
return Colors.green;
}
}
return const Color(0xFF6949FD);
}
Widget getIconForOption(Option option, Question _question) {
final isSelected = option == _question.selectedOption;
if (_question.isLocked) {
if (isSelected) {
return option.isCorrect
? const Icon(Icons.check_circle, color: Colors.green)
: const Icon(Icons.cancel, color: Colors.red);
} else if (option.isCorrect) {
return const Icon(Icons.check_circle, color: Colors.green);
}
}
return const SizedBox.shrink();
}
}
An easier way is to restart the app when you go back or press a button. You can wrap Scaffold() with WillPopScope() to restart when you back. You can use this package to restart.
If you need to save the score, you can save it in local storage. Another easy package for this is get_storage.
dependencies:
flutter_phoenix: ^1.1.0
runApp(Phoenix(child: const MyApp()));
WillPopScope(
onWillPop: () async {
Phoenix.rebirth(context);
},
child: Scaffold())

flutter riverpod leaving screen then come back it doesn't maintain the state

So I have two screens:
-Book_screen to display all the books(click on any book to go to article_screen)
-article_screen to display articles
In article_screen, I can click on article to save it as favorites.
but when I go back to book_screen then come back to article_screen, those favorited articles doesn't show the favorited status(icon red heart).
this is my article screen code:
class ArticleENPage extends ConsumerStatefulWidget{
final String bookName;
const ArticleENPage({Key? key,#PathParam() required this.bookName,}) : super(key: key);
#override
ArticleENScreen createState()=> ArticleENScreen();
}
class ArticleENScreen extends ConsumerState<ArticleENPage> {
late Future<List<Code>> codes;
#override
void initState() {
super.initState();
codes = fetchCodes();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.bookName,style: const TextStyle(fontSize: 24,fontWeight: FontWeight.bold),),backgroundColor: Colors.white,foregroundColor: Colors.black,elevation: 0,),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0),
child: Container(
margin: const EdgeInsets.only(top:10),
height: 43,
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
border: Border.all(
color: Colors.black.withOpacity(0.32),
),
),
child: Consumer(
builder: (context,ref,_) {
return TextField(
onChanged: (value) {
searchStringController controller = ref.read(searchStringProvider.notifier);
controller.setText(value.toLowerCase());
},
decoration: const InputDecoration(
border: InputBorder.none,
icon: Icon(Icons.search,size:18),
hintText: "Search Here",
hintStyle: TextStyle(color: Color.fromRGBO(128,128, 128, 1)),
),
);
}
),
),
),
const SizedBox(height: 10),
Expanded(
child: FutureBuilder(
builder: (context, AsyncSnapshot<List<Code>> snapshot) {
if (snapshot.hasData) {
return Center(
child: Consumer(
builder: (context,ref,child) {
final searchString = ref.watch(searchStringProvider);
return ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, int index) {
return snapshot.data![index].name
.toLowerCase()
.contains(searchString) ||
snapshot.data![index].description
.toLowerCase()
.contains(searchString)
? Consumer(
builder: (context,ref,child) {
final favlist = ref.watch(FavoriteListController.favoriteListProvider);
print(favlist);
final alreadySaved = favlist.contains(snapshot.data![index]);
return Card(
child:Padding(
padding: const EdgeInsets.all(10),
child:ExpandableNotifier(
child: ScrollOnExpand(
child: ExpandablePanel(
theme: const ExpandableThemeData(hasIcon: true),
header: RichText(text: TextSpan(children: highlight(snapshot.data![index].name, searchString,'title')),),
collapsed: RichText(text: TextSpan(children: highlight(snapshot.data![index].description, searchString,'content')), softWrap: true, maxLines: 3, overflow: TextOverflow.ellipsis,),
expanded: Column(
children: [
RichText(text: TextSpan(children: highlight(snapshot.data![index].description, searchString,'content')), softWrap: true ),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
semanticLabel: alreadySaved ? 'Remove from saved' : 'Save',
),
onPressed: () {
FavoriteListController controller = ref.read(FavoriteListController.favoriteListProvider.notifier);
if (alreadySaved) {
controller.toggle(snapshot.data![index]);
} else {
controller.toggle(snapshot.data![index]);
}
},
),
IconButton(
icon: const Icon(Icons.content_copy),
onPressed: () {
setState(() {
Clipboard.setData(ClipboardData(text: snapshot.data![index].name+"\n"+snapshot.data![index].description))
.then((value) {
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(content: Text('Copied')));
},);
});
},
),],),],)),),)));})
: Container();
},
separatorBuilder: (BuildContext context, int index) {
return snapshot.data![index].name
.toLowerCase()
.contains(searchString) ||
snapshot.data![index].description
.toLowerCase()
.contains(searchString)
? Divider()
: Container();
},
);
}
),
);
} else if (snapshot.hasError) {
return const Center(child: Text('Something went wrong :('));
}
return const Align(alignment:Alignment.topCenter,child:CircularProgressIndicator());
},
future: codes,
),
),
],
),
);
}
//read from files
Future<List<Code>> fetchCodes() async {
final response =
await rootBundle.loadString('assets/articles.json');
var CodeJson = json.decode(response)[widget.bookName] as List<dynamic>;
return CodeJson.map((code) => Code.fromJson(code)).toList();
}
}
I tried using riverpod for provider and save to sharedpreference the list of code that I favorited.
final sharedPrefs =
FutureProvider<SharedPreferences>((_) async => await SharedPreferences.getInstance());
class FavoriteListController extends StateNotifier<List<Code>>{
FavoriteListController(this.pref) : super(Code.decode(pref?.getString("favcode")??""));
static final favoriteListProvider = StateNotifierProvider<FavoriteListController, List<Code>>((ref) {
final pref = ref.watch(sharedPrefs).maybeWhen(
data: (value) => value,
orElse: () => null,
);
print(pref?.getString("favcode"));
return FavoriteListController(pref);
});
final SharedPreferences? pref;
void toggle(Code code) {
if (state.contains(code)) {
state = state.where((id) => id != code).toList();
} else {
state = [...state, code];
}
final String encodedData = Code.encode(state);
pref!.setString("favcode", encodedData);
}
}
I am not sure what is the cause of this but I think it might be because of futurebuilder? I am confused to how to solve this issue...
I am stuck in a dead end so any help or advice would be really appreciated
edit 1-
this is my source code in case I have not include all the necessary codes
https://github.com/sopheareachte/LawCode
edit-2
do I need to change "late Future<List> codes;" that fetch all the codes for futurebuilder to riverpod futureprovider too for it to work?
Maybe the problem is, that you define a static provider inside of your controller class. Try this code:
final sharedPrefs = FutureProvider<SharedPreferences>((_) async => await SharedPreferences.getInstance());
final favoriteListProvider = StateNotifierProvider<FavoriteListController, List<Code>>((ref) {
final pref = ref.watch(sharedPrefs).maybeWhen(
data: (value) => value,
orElse: () => null,
);
print(pref?.getString("favcode"));
return FavoriteListController(pref);
});
class FavoriteListController extends StateNotifier<List<Code>>{
FavoriteListController(this.pref) : super(Code.decode(pref?.getString("favcode")??""));
final SharedPreferences? pref;
void toggle(Code code) {
if (state.contains(code)) {
state = state.where((id) => id != code).toList();
} else {
state = [...state, code];
}
final String encodedData = Code.encode(state);
pref!.setString("favcode", encodedData);
}
}

getx obx not updating avatar image - Flutter GetX

what I want to achieve is to change the image in CircleAvatar when I'm selecting an image, here is the code:
ProfileController:
class ProfileController extends GetxController {
final TextEditingController emailController = TextEditingController();
final ImagePicker _picker = ImagePicker();
Rx<String?> avatarPath = null.obs;
avatarFromCamera() async {
var localAvatar = await _picker.pickImage(
source: ImageSource.camera, imageQuality: 50
);
if (localAvatar != null) {
avatarPath = localAvatar.path.obs;
update();
}
}
avatarFromGallery() async {
var localAvatar = await _picker.pickImage(
source: ImageSource.gallery, imageQuality: 50
);
if (localAvatar != null) {
avatarPath = localAvatar.path.obs;
update();
}
}
String? emailValidator(String? value) {
if (value == null || value.isEmpty) {
return null;
}
if (!EmailValidator.validate(value, false)) {
return 'Invalid email address';
}
}
#override
void onClose() {
emailController.dispose();
super.onClose();
}
String? emailValidator(String? value) {
if (value == null || value.isEmpty) {
return null;
}
if (!EmailValidator.validate(value, false)) {
return 'Invalid email address';
}
}
void save(GlobalKey<FormState> profileFormKey) {
if (profileFormKey.currentState!.validate()) {
print('valid');
}
}
}
and here is the ProfileScreen widget:
lass ProfileScreen extends StatelessWidget {
final ProfileController _profileController = Get.put<ProfileController>(ProfileController());
GlobalKey<FormState> profileFormKey = GlobalKey<FormState>();
ProfileScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Update user details'),
),
body: SingleChildScrollView(
child: Form(
key: profileFormKey,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(30.0),
child: TextFormField(
keyboardType: TextInputType.text,
controller: _profileController.emailController,
decoration: const InputDecoration(
labelText: 'Enter email',
),
validator: _profileController.emailValidator,
),
),
Center(
child: GestureDetector(
onTap: () {
showModalBottomSheet(
context: context,
builder: (BuildContext bc) {
return SafeArea(
child: Wrap(
children: <Widget>[
ListTile(
leading: const Icon(Icons.photo_library),
title: const Text('Photo Library'),
onTap: () {
_profileController.avatarFromGallery();
Navigator.of(context).pop();
}),
ListTile(
leading: const Icon(Icons.photo_camera),
title: const Text('Camera'),
onTap: () {
_profileController.avatarFromCamera();
Navigator.of(context).pop();
},
),
],
),
);
}
);
},
child: CircleAvatar(
radius: 55,
backgroundColor: Colors.pink,
child: Obx(() =>(_profileController.avatarPath.value != null)
? ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Image.file(
File(_profileController.avatarPath.value!),
width: 100,
height: 100,
fit: BoxFit.fitHeight
),
)
: Container(
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(50)),
width: 100,
height: 100,
child: Icon(
Icons.camera_alt,
color: Colors.grey[800],
),
),
),
),
)),
Container(
margin: const EdgeInsets.all(10),
width: double.infinity,
child: MaterialButton(
color: Colors.blue,
onPressed: () => _profileController.save(profileFormKey),
child: const Text(
'Submit',
style: TextStyle(color: Colors.white),
),
),
),
],
),
),
),
);
}
}
as you can see, I have Obx and my avatarPath reactive, and I'm running update everywhere I changing it, but it's not udpated. I also tried to use empty string as initial value of imagePath like this Rx<String> avatarPath = ''.obs; and it's not working. What I'm doing wrong??? Thank you in advice!!!
There are two things to revise for it. Firstly, change avatarPath = localAvatar.path.obs; with avatarPath.value = localAvatar.path;. Because localAvatar.path.obs create the new observable and changes will not be reflected to previous observers.
Secondly, create a new stateless widget having the widget tree of bottom sheet's builder like
showModalBottomSheet(
context: context,
builder: (BuildContext bc) {
return CustomBottomView();
}
);
Then inside the CustomBottomView copy your bottom sheet widget tree.
class CustomBottomView extends GetView<ProfileController> {
return YourBottomSheetWidgetTreeHere();
}
Dont worry about ProfileController here. You have already put it in DI in previous route. First follow the first step if you still face the problem second step will definitely resolve it.

Unhandled Exception: Bad state: Cannot add new events after calling close?

I'm making a login feature, I'm making fire responses to find out whether responses were successful or failed. can see my BLOC coding. the login process was successful but when I want to return to the login page after logging out the error appears Unhandled Exception: Bad state: Cannot add new events after calling close. how can I handle it?
API RESPONSES :
class ApiResponse<T> {
Status status;
T data;
String message;
ApiResponse.loading(this.message) : status = Status.LOADING;
ApiResponse.completed(this.data) : status = Status.COMPLETED;
ApiResponse.error(this.message) : status = Status.ERROR;
// #override
// String toString() {
// return "Status : $status \n Message : $message \n Data : $data";
// }
}
enum Status { LOADING, COMPLETED, ERROR }
BLOC :
class LoginBloc extends Object with Validators{
final _repository = EresidenceRepository();
final _userid = BehaviorSubject<String>();
final _password = BehaviorSubject<String>();
final _imei = BehaviorSubject<String>();
final _coordinate = BehaviorSubject<String>();
final BehaviorSubject<ApiResponse<login_responses>> _subject = BehaviorSubject<ApiResponse<login_responses>>();
Function(String) get userid => _userid.sink.add;
Function(String) get password => _password.sink.add;
Function(String) get imei => _imei.sink.add;
Function(String) get coordinate => _coordinate.sink.add;
Stream<String> get useridValidation => _userid.stream.transform(useridValidator);
Stream<String> get passwordValidation => _password.stream.transform(passwordValidator);
Stream<bool> get submitCheck => Rx.combineLatest2(useridValidation, passwordValidation, (e,p) => true);
login() async {
_subject.sink.add(ApiResponse.loading("Logging In..."));
try {
login_responses response = await _repository.login(
_userid.value, _password.value, _imei.value, _coordinate.value);
prefsBloc.changePrefsLogin(
PrefsState(false, response.data.userid, response.data.password, _imei.value, _coordinate.value, "")
);
_subject.sink.add(ApiResponse.completed(response));
print(response);
} catch (e) {
_subject.sink.add(ApiResponse.error(e.toString()));
print(e);
}
}
dispose(){
_userid.close();
_password.close();
_imei.close();
_coordinate.close();
_subject.close();
}
BehaviorSubject<ApiResponse<login_responses>> get subject => _subject;
}
final login = LoginBloc();
UI :
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> with WidgetsBindingObserver{
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
FocusNode passwordFocusNode, useridFocusNode;
#override
void initState() {
super.initState();
prefsBloc.checkLoginPref(context);
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.dark,
child: Scaffold(
backgroundColor: Colors.white,
body: StreamBuilder(
stream: login.subject,
builder: (context, AsyncSnapshot<ApiResponse<login_responses>> snapshot){
if(snapshot.hasData) {
print(snapshot.data.status);
switch (snapshot.data.status) {
case Status.LOADING:
_onWidgetDidBuild((){
Scaffold.of(context).showSnackBar(
SnackBar(
content: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(snapshot.data.message),
CircularProgressIndicator(),
],
),
backgroundColor: Colors.black,
),
);
});
break;
case Status.COMPLETED:
login_responses result = snapshot.data.data;
if(result.data.bit70 == "000") {
_onWidgetDidBuild((){
login.dispose();
AppRoutes.replace(context, LoginVerifyPage());
});
}else{
_onWidgetDidBuild((){
login.dispose();
AppRoutes.replace(context, MainApp());
});
}
break;
case Status.ERROR:
_onWidgetDidBuild(() {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('${snapshot.data.message}'),
backgroundColor: Colors.red,
));
});
break;
}
}
return _formLogin();
}
),
)
);
}
_formLogin() {
return SafeArea(
child: Container(
child: Column(
children: <Widget>[
Expanded(
flex: 1,
child: Container(
padding: EdgeInsets.symmetric(horizontal: SizeConfig.widthMultiplier * 1, vertical: SizeConfig.heightMultiplier * 1),
child: CachedNetworkImage(
imageUrl: "https://images.glints.com/unsafe/1024x0/glints-dashboard.s3.amazonaws.com/company-logo/68545821966f833d182f98775c73c7ae.png",
errorWidget: (context, url, error) => Icon(Icons.broken_image),
fit: BoxFit.fill,
),
)
),
Expanded(
flex: 2,
child: Container(
padding: EdgeInsets.all(SizeConfig.heightMultiplier * 2),
child: Column(
children: <Widget>[
Container(
child: StreamBuilder<String>(
stream: login.useridValidation,
builder: (context, snapshot) => DataTextField(
errorText: snapshot.error,
hintText: "No Handphone",
textInputAction: TextInputAction.next,
icon: Icons.phone,
onSubmitted: () => FocusScope.of(context).requestFocus(passwordFocusNode),
onChanged: login.userid,
keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
),
)
),
Container(
margin: EdgeInsets.only(top: SizeConfig.heightMultiplier * 2),
child: StreamBuilder<String>(
stream: login.passwordValidation,
builder: (context, snapshot) => PasswordTextField(
errorText: snapshot.error,
hintText: "Password",
textInputAction: TextInputAction.done,
onSubmitted: () {
FocusScope.of(context).requestFocus(FocusNode());
},
onChanged: login.password,
focusNode: passwordFocusNode,
),
)
),
Container(
width: SizeConfig.screenWidth,
margin: EdgeInsets.only(top: SizeConfig.heightMultiplier * 2.5),
child: GestureDetector(
onTap: () => AppRoutes.push(context, ForgotPasswordPage()),
child: Text(
Strings.titleForgotPass+" ?",
style: AppTheme.styleSubTitlePurpel,
textAlign: TextAlign.right,
),
)
),
Container(
width: SizeConfig.screenWidth,
margin: EdgeInsets.only(top: SizeConfig.heightMultiplier * 5),
child: StreamBuilder<bool>(
stream: login.submitCheck,
builder: (context, snapshot) => AppButton(
onPressed: snapshot.hasData ? () => login.login() : null,
text: Strings.signin
),
)
)
],
)
)
),
Expanded(
flex: 1,
child: Container(
alignment: Alignment.bottomCenter,
margin: EdgeInsets.only(bottom: SizeConfig.heightMultiplier * 2.5),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
Strings.dontAccount,
style: AppTheme.styleSubTitleBlackSmall,
textAlign: TextAlign.right,
),
Container(
margin: EdgeInsets.only(left: SizeConfig.widthMultiplier * 1),
child: InkWell(
onTap: () => AppRoutes.push(context, RegistrationPage()),
child: Text(
Strings.registration,
style: AppTheme.styleSubTitlePurpel,
textAlign: TextAlign.right,
),
)
)
],
)
)
)
],
),
),
);
}
void _onWidgetDidBuild(Function callback) {
WidgetsBinding.instance.addPostFrameCallback((_) {
callback();
});
}
}
The problem is that you have created an instance of your bloc globally (which is not a good practice), and after the login process is done you have called login.dispose() which closes all of the streams in your LoginBloc, and you can't add new events to closed streams.
You'd better create an instance of your LoginBloc in your LoginPage initState method, and close it in the dispose method.
This way, whenever you navigate to login page, a new bloc is created and it would work as expected.
UPDATE:
A simple example:
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
LoginBloc _loginBloc;
#override
void initState() {
super.initState();
_loginBloc = LoginBloc();
}
#override
void dispose() {
_loginBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container();
}
}