Handle callback response from Post method with bloc - flutter

I am calling login api on button click, I am able to get response from server but on clicking on button ,i am not able to received response.How can i receive the response and handle it .I am using BLoC pattern for this. Here is the code,
import 'package:flutter/material.dart';
import '../blocs/bloc.dart';
import '../blocs/provider.dart';
import '../models/login_response.dart';
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Provider(
child: new Scaffold(
body: Container(
child: LoginForm(),
),
),
);
}
}
class LoginForm extends StatefulWidget {
// since its a stateful widget we need to create state for it.
const LoginForm({Key key}) : super(key: key);
#override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
#override
Widget build(BuildContext context) {
return Form(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 50),
),
// Start creating widget here.
emailField(),
passwordField(),
Container(margin: EdgeInsets.only(top: 25.0)),
submitButton()
],
),
);
}
Widget emailField() {
return StreamBuilder(
stream: bloc.email,
builder: (context, snapshot) {
return TextField(
onChanged: bloc.changeEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'you#example.com',
labelText: 'Email Address',
errorText: snapshot.error
),
);
}
);
}
Widget passwordField() {
return StreamBuilder(
stream: bloc.password,
builder: (context, snapshot) {
return TextField(
onChanged: bloc.changePassword,
obscureText: true,
decoration: InputDecoration(
labelText: 'Please enter your password',
hintText: 'Password',
errorText: snapshot.error
),
);
},
);
}
Widget submitButton() {
return StreamBuilder(
stream: bloc.submitValid,
builder: (context, snapshot) {
return RaisedButton(
onPressed:() => showWidgetForNetworkCall(context),
// onPressed: () {
// // Do submit button action.
// showWidgetForNetworkCall(context);
// // callLoginApi();
// },
child: const Text('Login'),
textColor: Colors.white,
color: Colors.blueAccent,
);
},
);
}
// Loading Widget
Widget _buildLoadingWidget() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Loading data from API...", textDirection: TextDirection.ltr), CircularProgressIndicator()
],
),
);
}
// // Error Widget
Widget _buildErrorWidget(String error) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Loading error data from API...", textDirection: TextDirection.ltr), CircularProgressIndicator()
],
),
);
}
// show server data
showServerData() {
print(" Servr >>>>>> Data : ");
}
Widget showWidgetForNetworkCall(BuildContext context) {
bloc.loginSubmit();
return StreamBuilder(
stream: bloc.loginSubject.stream,
builder: (context, AsyncSnapshot<LoginResponse>snapshot){
if (snapshot.hasData) {
return showServerData();
} else if (snapshot.hasError) {
return _buildErrorWidget(snapshot.error);
} else {
return _buildLoadingWidget();
}
},
);
}
}
This is my login_screen.dart. And my bloc class for api call is:
postData() async {
LoginResponse response = await _repository.postData(_loginResource);
_subject.sink.add(response);
}
code of bloc
import 'dart:async';
import 'package:rxdart/rxdart.dart';
import 'validators.dart';
import '../models/login_response.dart';
import '../repository/login_repository.dart';
import '../resources/login_resource.dart';
class Bloc extends Object with Validators {
final LoginRepository _repository = LoginRepository();
final BehaviorSubject<LoginResponse> _subject =
BehaviorSubject<LoginResponse>();
LoginResource _loginResource = LoginResource();
final _email = BehaviorSubject<String>(); // Declaring variable as private
final _password = BehaviorSubject<String>(); // Declaring variable as private
// Add data to stream (Its like setter)
Stream<String> get email => _email.stream.transform(validateEmail);
Stream<String> get password =>
_password.stream.transform(validatePassword);
Stream<bool> get submitValid => Observable.combineLatest2(email, password, (e, p) => true);
// Change data. For retrieveing email value.
Function(String) get changeEmail => _email.sink.add;
Function(String) get changePassword => _password.sink.add;
loginSubmit() {
_loginResource.email = "bar1";
_loginResource.password = "bar2";
postData();
}
postData() async {
LoginResponse response = await _repository.postData(_loginResource);
_subject.sink.add(response);
}
dispose() {
_email.close();
_password.close();
_subject.close();
}
BehaviorSubject<LoginResponse> get loginSubject => _subject;
}
final bloc = Bloc();

Related

The search bar does not return any results

I am trying to add a search function in my flutter app, the search bar is showing and there's not errors but its not working and it doesn't return any results.
the data list is from an API that I already called using the rest API
// ignore_for_file: use_key_in_widget_constructors, avoid_print, avoid_unnecessary_containers, curly_braces_in_flow_control_structures, prefer_const_constructors, non_constant_identifier_names, unnecessary_new, avoid_function_literals_in_foreach_calls, unused_import, avoid_types_as_parameter_names, unused_label
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:myapp2/Service_Request/SR.dart';
import 'package:myapp2/main.dart';
import 'package:myapp2/Service_Request/second.dart';
import '../Classes/demandes.dart';
import 'SR_details.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DataFromAPI(),
);
}
}
class DataFromAPI extends StatefulWidget {
#override
_DataFromAPIState createState() => _DataFromAPIState();
}
List<Attributes> _MyAllData = [];
var _srAttributes = [];
class _DataFromAPIState extends State<DataFromAPI> {
#override
void initState() {
loadData().then((value) {
setState(() {
_srAttributes.addAll(value);
});
});
super.initState();
}
Future<List<Sr>> loadData() async {
try {
var response = await http.get(Uri.parse(
'http://192.168.1.30:9080/maxrest/rest/mbo/sr/?_lid=azizl&_lpwd=max12345m&_format=json'));
if (response.statusCode == 200) {
final jsonBody = json.decode(response.body);
Demandes data = Demandes.fromJson(jsonBody);
final srAttributes = data.srMboSet.sr;
return srAttributes;
}
} catch (e) {
throw Exception(e.toString());
}
throw Exception("");
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: new Scaffold(
appBar: AppBar(
title: Text('Liste des Demandes'),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.push(
context, MaterialPageRoute(builder: (context) => SR()))),
),
body: FutureBuilder<List<Sr>?>(
future: loadData(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
} else {
return new ListView.builder(
itemCount: snapshot.data?.length,
itemBuilder: ((_, index) {
return index == 0
? _searchbar()
: new ListTile(
title: new Card(
margin: new EdgeInsets.symmetric(
vertical: 2.0, horizontal: 8.0),
elevation: 10,
child: new ListTile(
title: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(padding: new EdgeInsets.all(2.0)),
new Text(
'Ticket ID : ${snapshot.data![index].attributes.ticketid.content}'),
new Text(
'status : ${snapshot.data![index].attributes.status.content}'),
new Text(
'description : ${snapshot.data![index].attributes.description?.content}'),
new Text(
'Reported by : ${snapshot.data![index].attributes.reportedby.content}'),
new Text(
'Reoprt date : ${snapshot.data![index].attributes.statusdate.content}'),
],
),
trailing: Icon(Icons.arrow_forward_ios_rounded),
),
),
onTap: () {
Navigator.of(context)
.push(
new MaterialPageRoute(
builder: (BuildContext context) =>
new SrDetailsScreen(
sr: snapshot.data![index]),
),
)
.then((data) {});
});
}),
);
}
},
),
),
);
}
_searchbar() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
decoration: InputDecoration(hintText: "Search ..."),
onChanged: (text) {
text = text.toLowerCase();
setState(() {
_srAttributes = _MyAllData.where((srAttributes) {
var idticket = srAttributes.description!.content.toLowerCase();
return idticket.contains(text);
}).toList();
});
},
),
);
}
}
FutureBuilder loads values of current future. You are assigning a function result to FutureBuilder so its value always changes dynamically.
Create variable to keep Future's value.
Future<List<Sr>>? dataToLoad;
Whenever you want to load data from server ( for example, on text changed ):
setState((){
dataToLoad = loadData();
});
And use it in FutureBuilder:
FutureBuilder<List<Sr>?>(
future: dataToLoad,

How can i pass snapshot data from futurebuilder to another futurebuilder in the same page?

I'm new to the flutter world and mobile app development and struggling with how I should pass data throughout my app. This is my code, How can I pass snapshot data from futurebuilder to another futurebuilder on the same page? help, please
**Widget _buildProgrammCard()**
From this widget Card I need to pass the location id which is in the futurebuilder to another futurebuilder.
Widget _buildProgrammCard() {
return Container(
height: 90,
child:
Card(
semanticContainer: true,
clipBehavior: Clip.antiAliasWithSaveLayer,
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
elevation: 4,
margin: EdgeInsets.fromLTRB(14, 0, 14, 14),
child:
FutureBuilder(
future: databaseHelper2.Lastlocation(),
builder: (context,snapshot) {
if (snapshot.hasError)
{
print(snapshot.error);
print("there is problem");
}
return snapshot.hasData
? Text("Location :" +snapshot.data.id)
: Center(child: CircularProgressIndicator(
),
);
}
),
),
);
}
Widget build(BuildContext context)
And this is the second Widget that I need to pass the location id into it from another widget.
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[100],
body: FutureBuilder(
future: databaseHelper2.Getweither(location_id),
builder: (context,snapshot) {
if (snapshot.hasError)
{
print(snapshot.error);
print("there is problem !");
}
return snapshot.hasData
? ItemList(list: snapshot.data)
: Center(child: CircularProgressIndicator(
),
);
}
),
);
}
Flutter rebuilds widgets often so FutureBuilder shouldn't call a future function directly. (A widget may call its build function up to 60 times a second.)
Instead a FutureBuilder should only receive a future value from an async function called elsewhere.
In a StatefulWidget, the most common place to initiate long-running operations is in its initState() method.
The location data, retrieved during the first Widget initState, can be passed to the second widget, just like a regular constructor argument.
You'll access it in the 2nd widget's State class with widget.locationId.
import 'package:flutter/material.dart';
class FirstFuturePage extends StatefulWidget {
#override
State<StatefulWidget> createState() => FirstFutureState();
}
class FirstFutureState extends State<FirstFuturePage> {
Future<int> locationId = Future.value(-1);
#override
void initState() {
// TODO: implement initState
super.initState();
someAsyncCall();
}
Future<void> someAsyncCall() async {
// just returns the number 0 after 2 seconds & assigns it to "locationId" var
locationId = Future.delayed(Duration(seconds: 2), () => 0);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: FutureBuilder<int>(
future: locationId,
builder: (context, snapshot) {
int _locationId = snapshot.data;
if (snapshot.hasData)
return SecondWidget(_locationId);
return Text('Looking up location...');
},
),
),
),
);
}
}
class SecondWidget extends StatefulWidget {
final int locationId;
SecondWidget(this.locationId);
#override
_SecondWidgetState createState() => _SecondWidgetState();
}
class _SecondWidgetState extends State<SecondWidget> {
Future<String> weatherData = Future.value('Unknown');
#override
void initState() {
super.initState();
loadWeather(widget.locationId); // Use the locationId passed into widget
}
/// Takes locationId from First widget and looks up weather data for location
Future<void> loadWeather(int locationId) async {
List<String> weatherDataStore = List<String>.from(['Rainy', 'Sunny']);
weatherData = Future.delayed(
Duration(seconds: 2), () => weatherDataStore[locationId]
);
}
#override
Widget build(BuildContext context) {
int _locId = widget.locationId;
return FutureBuilder<String>(
future: weatherData,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('Weather for location $_locId is: ${snapshot.data}');
}
return Text('Loading Weather...');
},
);
}
}
State Management Solutions
When you get tired of passing values around like in the above example, you can use a State Management package or create your own that suits your needs.
Here's a nice overview from Jeff Delaney about various options:
https://fireship.io/lessons/flutter-state-management-guide/
And also check out Get which isn't mentioned in the above:
https://pub.dev/packages/get
Some of the above State management solutions (e.g. Provider) help you use Flutter-native state functionality correctly (because its rather complicated), while others completely avoid that and provide a framework separate from the Widget lifecycle (e.g. Get).
Thanks Baker for your response but not exactly what i meant
these are my two futures and my class
This is my future that returns the Location from her i need to pass the location id to the another future
Future<Location> Lastlocation() async {
final prefs = await SharedPreferences.getInstance();
final key = 'token';
final value = prefs.get(key) ?? 0;
String myUrl = "$serverUrl/location/getlastlocation?token=" + value;
http.Response response = await http.get(
myUrl,
headers: {
'Accept': 'application/json',
//'Authorization': 'token $value'
},
);
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
return Location.fromJson(json.decode(response.body));
} else {
// then throw an exception.
throw Exception('Failed to load album');
}
}
This is my future that returns List of weather that depends on location id
Future<List> Getweither(String ID) async {
final prefs = await SharedPreferences.getInstance();
final key = 'token';
final value = prefs.get(key) ?? 0;
String myUrl = "$serverUrl/sensors/getDeviceByid/$ID?token=" + value;
http.Response response = await http.get(myUrl,
headers: {
'Accept': 'application/json',
});
print("myUrldevice :"+myUrl);
print("status :"+response.statusCode.toString());
return json.decode(response.body);
}
This is my class Location
// To parse this JSON data, do
//
// final location = locationFromJson(jsonString);
import 'dart:convert';
List<Location> locationFromJson(String str) => List<Location>.from(json.decode(str).map((x) => Location.fromJson(x)));
String locationToJson(List<Location> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class Location {
Location({
this.automaticIrrigation,
this.coordinates,
this.createdDate,
this.sensorIds,
this.id,
this.siteName,
this.description,
this.v,
});
bool automaticIrrigation;
List<double> coordinates;
DateTime createdDate;
List<String> sensorIds;
String id;
String siteName;
String description;
int v;
factory Location.fromJson(Map<String, dynamic> json) => Location(
automaticIrrigation: json["AutomaticIrrigation"],
coordinates: List<double>.from(json["Coordinates"].map((x) => x.toDouble())),
createdDate: DateTime.parse(json["Created_date"]),
sensorIds: List<String>.from(json["Sensor_ids"].map((x) => x)),
id: json["_id"],
siteName: json["SiteName"],
description: json["Description"],
v: json["__v"],
);
Map<String, dynamic> toJson() => {
"AutomaticIrrigation": automaticIrrigation,
"Coordinates": List<dynamic>.from(coordinates.map((x) => x)),
"Created_date": createdDate.toIso8601String(),
"Sensor_ids": List<dynamic>.from(sensorIds.map((x) => x)),
"_id": id,
"SiteName": siteName,
"Description": description,
"__v": v,
};
}
and this is my homePage
import 'dart:convert';
import 'dart:developer';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sidebar_animation/Services/DataHelpers.dart';
import 'package:sidebar_animation/sidebar/sidebar_layout.dart';
import '../bloc.navigation_bloc/navigation_bloc.dart';
import 'package:sidebar_animation/constants.dart';
import 'package:flutter/gestures.dart';
import 'package:sidebar_animation/bloc.navigation_bloc/navigation_bloc.dart';
class HomePage extends StatelessWidget with NavigationStates {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
DatabaseHelper2 databaseHelper2 = new DatabaseHelper2();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[100],
body: ListView(
FutureBuilder(
future: databaseHelper2.Getweither(location_id),
builder: (context,snapshot) {
if (snapshot.hasError)
{
print(snapshot.error);
print("there is problem !");
}
return snapshot.hasData
? ItemList(list: snapshot.data)
: Center(child: CircularProgressIndicator(
),
);
}
),
);
}
Widget _buildProgrammCard() {
return Container(
height: 90,
child:
Card(
semanticContainer: true,
clipBehavior: Clip.antiAliasWithSaveLayer,
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
elevation: 4,
margin: EdgeInsets.fromLTRB(14, 0, 14, 14),
child:
FutureBuilder(
// future: databaseHelper.getData(),
future: databaseHelper2.Lastlocation(),
builder: (context,snapshot) {
if (snapshot.hasError)
{
print(snapshot.error);
print("mochkla lenaa *");
}
return snapshot.hasData
? Text("Location :" +snapshot.data.siteName)
: Center(child: CircularProgressIndicator(
),
);
}
),
),
);
}
class ItemList extends StatelessWidget{
List list;
ItemList({this.list});
ScrollController _controller = new ScrollController();
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: list == null ? 0 : list.length,
scrollDirection: Axis.horizontal,
itemExtent: 190.0,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 0, 14),
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
item.storyUrl,
),
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
Colors.black26,
BlendMode.darken,
),
),
borderRadius: BorderRadius.circular(10.0),
color: Colors.grey,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
"temp :",
style: TextStyle(color: Colors.white),
),
),
Padding(
padding: EdgeInsets.only(left: 24),
child:
Text(
list[i]['Weither']['Temp'],
),
),
],
),
),
);
},
),
}
}
Finaly i need to get the location id from the first future that return Location to the second future Getweither(String ID) .

flutter update value in another file widget

I'm new in flutter and I try to update a variable in my main.dart from a customWidget in another file.
I search on internet but I'm a little lost.
How to update my variable userDistance with the value that the user enter ?
Is my widget structure correct? or does it have to be stateless or stateful?
Main.dart :
class HomeController extends StatefulWidget {
HomeController({Key key, this.title}) : super(key: key);
final String title;
#override
_HomeControllerState createState() => _HomeControllerState();
}
class _HomeControllerState extends State<HomeController> {
// ************************************
// Variable
// ************************************
int userDistance = null;
final _formKey = GlobalKey<FormState>();
// ************************************
// Initialisation
// ************************************
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FutureBuilder(
future: loadJson(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
return SingleChildScrollView(
padding: EdgeInsets.all(24),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
FormValidatorWidgets.buildDistance(), // => Here I call my external widget
...
And my customWidget :
import 'package:flutter/material.dart';
class FormValidatorWidgets{
static buildDistance() {
return TextFormField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Distance",
hintText: "Enter a distance",
),
keyboardType: TextInputType.number,
validator: (String value) {
int distance = int.tryParse(value);
if (distance == null) {
return "Distance is required";
}
if (distance <= 0) {
return "Distance must be greater than zero";
}
},
onSaved: (String value) {
userDistance = int.tryParse(value);
},
);
}
}
How to update my variable userDistance ?
For your FormValidatorWidgets add callback function
class FormValidatorWidgets{
static buildDistance(FormFieldSetter<String> onSaved) { // onSaved is a callback
return TextFormField(
...
onSaved: onSaved,
);
}
}
And then in your form:
Column(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[
FormValidatorWidgets.buildDistance(
(String value) {
userDistance = int.tryParse(value);
},
),
...

How to add callback using BLoC pattern in flutter?

I am calling login api on button click, I am able to get response from server but on clicking on button it doesn't show progress bar. I am using BLoC pattern for this. Here is the code,
import 'package:flutter/material.dart';
import '../blocs/bloc.dart';
import '../blocs/provider.dart';
import '../models/login_response.dart';
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Provider(
child: new Scaffold(
body: Container(
child: LoginForm(),
),
),
);
}
}
class LoginForm extends StatefulWidget {
// since its a stateful widget we need to create state for it.
const LoginForm({Key key}) : super(key: key);
#override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
#override
Widget build(BuildContext context) {
return Form(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 50),
),
// Start creating widget here.
emailField(),
passwordField(),
Container(margin: EdgeInsets.only(top: 25.0)),
submitButton()
],
),
);
}
Widget emailField() {
return StreamBuilder(
stream: bloc.email,
builder: (context, snapshot) {
return TextField(
onChanged: bloc.changeEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'you#example.com',
labelText: 'Email Address',
errorText: snapshot.error
),
);
}
);
}
Widget passwordField() {
return StreamBuilder(
stream: bloc.password,
builder: (context, snapshot) {
return TextField(
onChanged: bloc.changePassword,
obscureText: true,
decoration: InputDecoration(
labelText: 'Please enter your password',
hintText: 'Password',
errorText: snapshot.error
),
);
},
);
}
Widget submitButton() {
return StreamBuilder(
stream: bloc.submitValid,
builder: (context, snapshot) {
return RaisedButton(
onPressed:() => showWidgetForNetworkCall(context),
// onPressed: () {
// // Do submit button action.
// showWidgetForNetworkCall(context);
// // callLoginApi();
// },
child: const Text('Login'),
textColor: Colors.white,
color: Colors.blueAccent,
);
},
);
}
// Loading Widget
Widget _buildLoadingWidget() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Loading data from API...", textDirection: TextDirection.ltr), CircularProgressIndicator()
],
),
);
}
// // Error Widget
Widget _buildErrorWidget(String error) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Loading error data from API...", textDirection: TextDirection.ltr), CircularProgressIndicator()
],
),
);
}
// show server data
showServerData() {
print(" Servr >>>>>> Data : ");
}
Widget showWidgetForNetworkCall(BuildContext context) {
bloc.loginSubmit();
return StreamBuilder(
stream: bloc.loginSubject.stream,
builder: (context, AsyncSnapshot<LoginResponse>snapshot){
if (snapshot.hasData) {
return showServerData();
} else if (snapshot.hasError) {
return _buildErrorWidget(snapshot.error);
} else {
return _buildLoadingWidget();
}
},
);
}
}
This is my login_screen.dart. And my bloc class for api call is:
postData() async {
LoginResponse response = await _repository.postData(_loginResource);
_subject.sink.add(response);
}
I am able to parse json api, but not able to get the response of my model i.e, 'LoginResponse' in login_screen.dart class and also the CircularProgressBar doesn't show when api is called on button click.
Code of the BLoC class is :
import 'dart:async';
import 'package:rxdart/rxdart.dart';
import 'validators.dart';
import '../models/login_response.dart';
import '../repository/login_repository.dart';
import '../resources/login_resource.dart';
class Bloc extends Object with Validators {
final LoginRepository _repository = LoginRepository();
final BehaviorSubject<LoginResponse> _subject =
BehaviorSubject<LoginResponse>();
LoginResource _loginResource = LoginResource();
final _email = BehaviorSubject<String>(); // Declaring variable as private
final _password = BehaviorSubject<String>(); // Declaring variable as private
// Add data to stream (Its like setter)
Stream<String> get email => _email.stream.transform(validateEmail);
Stream<String> get password =>
_password.stream.transform(validatePassword);
Stream<bool> get submitValid => Observable.combineLatest2(email, password, (e, p) => true);
// Change data. For retrieveing email value.
Function(String) get changeEmail => _email.sink.add;
Function(String) get changePassword => _password.sink.add;
loginSubmit() {
_loginResource.email = "bar1";
_loginResource.password = "bar2";
postData();
}
postData() async {
LoginResponse response = await _repository.postData(_loginResource);
_subject.sink.add(response);
}
dispose() {
_email.close();
_password.close();
_subject.close();
}
BehaviorSubject<LoginResponse> get loginSubject => _subject;
}
final bloc = Bloc();
Kindly let me know what I am missing. Thanks in advance :)
Well here we go. I make some changes in your UI layer and in BLoC class with order to accomplish what you're asking for. I will firstly show the pieces of code that I insert and explain what I was think when I wrote it and after all I will paste the entire source code will all changes. Maybe you can use the concept that I had used to adapt the source code to your needs. All code has comments so please read it will help you a lot.
First of all I create an enum to represent the status of the login process and a class that holds the login process status and a message about it. Both are part of your UI layer.
/// NON_LOGIN: means that login is not happening
/// LOGGIN: means that login is happening
/// LOGIN_ERROR: means that something is wrong with login
/// LOGIN_SUCCESS: the login process was a success.
enum LoginStatus { NON_LOGIN, LOGGING, LOGIN_SUCCESS, LOGIN_ERROR }
class LoginState {
final LoginStatus status;
final String message;
LoginState({this.status, this.message});
}
In _LoginFormState class inside build method I inserted a StreamBuilder that will show and hide the progressbar when the login is happening or show an error widget.
#override
Widget build(BuildContext context) {
return Form(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 50),
),
// Start creating widget here.
emailField(),
passwordField(),
Container(margin: EdgeInsets.only(top: 25.0)),
submitButton(),
StreamBuilder<LoginState>(
stream: bloc.loginStateStream,
builder: (context, AsyncSnapshot<LoginState> snapshot){
if ( !snapshot.hasData )
return Container();
switch(snapshot.data.status){
case LoginStatus.LOGGING:
return _buildLoadingWidget();
case LoginStatus.LOGIN_ERROR:
return _buildErrorWidget(snapshot.data.message);
case LoginStatus.LOGIN_SUCCESS:
// Here you can go to another screen after login success.
return Center(child: Text("${snapshot.data.message}"),);
case LoginStatus.NON_LOGIN:
default:
return Container();
}
},
),
],
),
);
}
And the last change in your UI layer was in submitButton method the only change was in onPress event of your button now it calls bloc.loginSubmit method.
return RaisedButton(
onPressed:() => bloc.loginSubmit(), // the only change
child: const Text('Login'),
textColor: Colors.white,
color: Colors.blueAccent,
);
Now all the changes are in BLoC class. Basically I created a new subject for handling the state changes of login process using LoginStatus enum and LoginState class and tell to view what widget must be showed to user.
//The subject and a get method to expose his stream
final PublishSubject<LoginState> _loginStateSubject = new PublishSubject();
Observable<LoginState> get loginStateStream => _loginStateSubject.stream;
All the login state changes handling I wrote inside postData method.
postData() async {
// this call will change the UI and a CircularProgressBar will be showed.
changeLoginState(state: LoginState( status: LoginStatus.LOGGING, message: "logging") );
// waiting for login response!
LoginResponse response = await _repository.postData(_loginResource);
print(response); // just to text debug your response.
//Here you can verify if the login process was successfully or if there is
// some kind of error based in your LoginResponse model class.
// avoiding write this logic in UI layer.
if(response.hasError){
changeLoginState(state: LoginState(status: LoginStatus.LOGIN_ERROR,
message: response.errorMessage)
);
// and after 1.5 seconds we make the error message disappear from UI.
// you can do this in UI layer too
Future.delayed(Duration(milliseconds: 1500), (){
// you can pass null to state property, will make the same effect
changeLoginState(state: LoginState(status: LoginStatus.NON_LOGIN)); });
}
else {
changeLoginState(state: LoginState(status:
LoginStatus.LOGIN_SUCCESS, message: "Login Success"));
}
//_subject.sink.add(response);
}
With this approach you avoid send to your UI layer objects from you model layer like LoginResponse class objects and this kind of concept makes your code more clean and do not broken MVC pattern and your UI layer holds only layout code.
Make some tests, I didn't, adapt to your needs and comment if you need something I will answer when I can.
The entire source code:
/// NON_LOGIN: means that login is not happening
/// LOGGIN: means that login is happening
/// LOGIN_ERROR: means that something is wrong with login
/// LOGIN_SUCCESS: the login process was a success.
///
enum LoginStatus { NON_LOGIN, LOGGING, LOGIN_SUCCESS, LOGIN_ERROR }
class LoginState {
final LoginStatus status;
final String message;
LoginState({this.status, this.message});
}
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Provider(
child: new Scaffold(
body: Container(
child: LoginForm(),
),
),
);
}
}
class LoginForm extends StatefulWidget {
// since its a stateful widget we need to create state for it.
const LoginForm({Key key}) : super(key: key);
#override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
#override
Widget build(BuildContext context) {
return Form(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 50),
),
// Start creating widget here.
emailField(),
passwordField(),
Container(margin: EdgeInsets.only(top: 25.0)),
submitButton(),
StreamBuilder<LoginState>(
stream: bloc.loginStateStream,
builder: (context, AsyncSnapshot<LoginState> snapshot){
if ( !snapshot.hasData )
return Container();
switch(snapshot.data.status){
case LoginStatus.LOGGING:
return _buildLoadingWidget();
case LoginStatus.LOGIN_ERROR:
return _buildErrorWidget(snapshot.data.message);
case LoginStatus.LOGIN_SUCCESS:
// Here you can go to another screen after login success.
return Center(child: Text("${snapshot.data.message}"),);
case LoginStatus.NON_LOGIN:
default:
return Container();
}
},
),
],
),
);
}
Widget emailField() {
return StreamBuilder(
stream: bloc.email,
builder: (context, snapshot) {
return TextField(
onChanged: bloc.changeEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'you#example.com',
labelText: 'Email Address',
errorText: snapshot.error
),
);
}
);
}
Widget passwordField() {
return StreamBuilder(
stream: bloc.password,
builder: (context, snapshot) {
return TextField(
onChanged: bloc.changePassword,
obscureText: true,
decoration: InputDecoration(
labelText: 'Please enter your password',
hintText: 'Password',
errorText: snapshot.error
),
);
},
);
}
Widget submitButton() {
return StreamBuilder(
stream: bloc.submitValid,
builder: (context, snapshot) {
return RaisedButton(
onPressed:() => bloc.loginSubmit(),
child: const Text('Login'),
textColor: Colors.white,
color: Colors.blueAccent,
);
},
);
}
// Loading Widget
Widget _buildLoadingWidget() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Loading data from API...", textDirection: TextDirection.ltr), CircularProgressIndicator()
],
),
);
}
// // Error Widget
Widget _buildErrorWidget(String error) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Loading error data from API...", textDirection: TextDirection.ltr), CircularProgressIndicator()
],
),
);
}
/*
// show server data
showServerData() {
print(" Servr >>>>>> Data : ");
}
Widget showWidgetForNetworkCall() {
return StreamBuilder(
stream: bloc.loginSubject.stream,
builder: (context, AsyncSnapshot<LoginResponse>snapshot){
if (snapshot.hasData) {
return showServerData();
} else if (snapshot.hasError) {
return _buildErrorWidget(snapshot.error);
} else {
return _buildLoadingWidget();
}
},
);
}*/
}
class Bloc extends Object with Validators {
//final BehaviorSubject<LoginResponse> _subject = BehaviorSubject<LoginResponse>();
//BehaviorSubject<LoginResponse> get loginSubject => _subject;
final LoginRepository _repository = LoginRepository();
final PublishSubject<LoginState> _loginStateSubject = new PublishSubject();
Observable<LoginState> get loginStateStream => _loginStateSubject.stream;
LoginResource _loginResource = LoginResource();
final _email = BehaviorSubject<String>(); // Declaring variable as private
final _password = BehaviorSubject<String>(); // Declaring variable as private
// Add data to stream (Its like setter)
Stream<String> get email => _email.stream.transform(validateEmail);
Stream<String> get password => _password.stream.transform(validatePassword);
Stream<bool> get submitValid => Observable.combineLatest2(email, password, (e, p) => true);
// Change data. For retrieveing email value.
Function(String) get changeEmail => _email.sink.add;
Function(String) get changePassword => _password.sink.add;
void changeLoginState({LoginState state } ) => _loginStateSubject.sink.add(state);
loginSubmit() {
_loginResource.email = "bar1";
_loginResource.password = "bar2";
postData();
}
postData() async {
// this call will change the UI and a CircularProgressBar will be showed.
changeLoginState(state: LoginState( status: LoginStatus.LOGGING, message: "logging") );
// waiting for login response!
LoginResponse response = await _repository.postData(_loginResource);
print(response); // just to text debug your response.
//Here you can verify if the login process was successfully or if there is
// some kind of error based in your LoginResponse model class.
if(response.hasError){
changeLoginState(state: LoginState(status: LoginStatus.LOGIN_ERROR,
message: response.errorMessage)
);
// and after 1.5 seconds we make the error message disappear from UI.
// you can do this in UI layer too
Future.delayed(Duration(milliseconds: 1500), (){
// you can pass null to state property, will make the same effect
changeLoginState(state: LoginState(status: LoginStatus.NON_LOGIN)); });
}
else {
changeLoginState(state: LoginState(status:
LoginStatus.LOGIN_SUCCESS, message: "Login Success"));
}
//_subject.sink.add(response);
}
dispose() {
_loginStateSubject.close();
_email.close();
_password.close();
//_subject.close();
}
}
final bloc = Bloc();

Flutter Auth (BLoC pattern & rxDart)

I wanted to make applications with authorization on the BLoC partner, but I encountered an error:
The following NoSuchMethodError was thrown building AuhtScreen(dirty, state: _AuhtScreenState<dynamic>#00539):
The getter 'blocState' was called on null.
Receiver: null
Tried calling: blocState
It is called under the following circumstances:
In AuhtScreen
AuthBloc authBloc = BlocProvider.of(context).authBloc; (context = StatefulElement)
In BlocProvider
static BlocState of(BuildContext context) { (context = StatefulElement)
return (context.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider).blocState; (context = StatefulElement)(NULL)
}
I do not understand why it does not work, I do everything correctly, maybe I missed something or did not understand... Help solve the problem!
All code:
AuthBloc
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:mifity/models/auht_detail.dart';
import 'package:mifity/models/user.dart';
import 'package:mifity/screens/main_screen.dart';
import 'package:mifity/services/auth_service.dart';
import 'package:rxdart/rxdart.dart';
class AuthBloc {
AuthService authService;
BuildContext _context;
final currentUserSubject = BehaviorSubject<User>.seeded(null);
final emailSubject = BehaviorSubject<String>.seeded('');
final passwordSubject = BehaviorSubject<String>.seeded('');
final loadingSubject = BehaviorSubject<bool>.seeded(false);
final loginSubject = BehaviorSubject<Null>.seeded(null);
//sink
void Function(String) get emailChanged => emailSubject.sink.add;
void Function(String) get passwordChanged => passwordSubject.sink.add;
void Function(BuildContext) get submitLogin => (context) {
this.setContext(context);
loginSubject.add(null);
};
//stream
Stream<User> get currentUser => currentUserSubject.stream;
Stream<String> get emailStream => emailSubject.stream;
Stream<String> get passwordStream => passwordSubject.stream;
Stream<bool> get loading => loadingSubject.stream;
AuthBloc({this.authService}) {
Stream<AuhtDetail> auhtDetailStream = Observable.combineLatest2(
emailStream, passwordStream, (email, password) {
return AuhtDetail(email: email, password: password);
});
Stream<User> loggedIn = Observable(loginSubject.stream)
.withLatestFrom(auhtDetailStream, (_, auhtDetail) {
return auhtDetail;
}).flatMap((auhtDetail) {
return Observable.fromFuture(authService.loginUser(auhtDetail))
.doOnListen(() {
loadingSubject.add(true);
}).doOnDone(() {
loadingSubject.add(false);
});
});
loggedIn.listen((User user) {
currentUserSubject.add(user);
Navigator.push(
_context,
new MaterialPageRoute(builder: (context) => MainScreen()),
);
}, onError: (error) {
Scaffold.of(_context).showSnackBar(new SnackBar(
content: new Text("Username or password incorrect"),
));
});
}
setContext(BuildContext context) {
_context = context;
}
close() {
emailSubject.close();
passwordSubject.close();
loadingSubject.close();
loginSubject.close();
}
}
BlocProvider
import 'package:flutter/material.dart';
import 'package:mifity/blocs/auth_bloc.dart';
import 'package:mifity/services/auth_service.dart';
class BlocProvider extends InheritedWidget {
final blocState = new BlocState(
authBloc: AuthBloc(authService: AuthService()),
);
BlocProvider({Key key, Widget child}) : super(key: key, child: child);
bool updateShouldNotify(_) => true;
static BlocState of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider)
.blocState;
}
}
class BlocState {
final AuthBloc authBloc;
BlocState({this.authBloc});
}
AuthService
import 'dart:async';
import 'package:mifity/models/auht_detail.dart';
import 'package:mifity/models/error.dart';
import 'package:mifity/models/user.dart';
class AuthService {
Future<User> loginUser(AuhtDetail detail) async {
await Future.delayed(Duration(seconds: 1)); //simulate network delay
if (detail.email == 'johndoe#acme.com' && detail.password == '1234') {
return User(
id: 1,
name: 'John Doe',
email: 'johndoe#acme.com',
age: 26,
profilePic: 'john_doe.png');
} else {
throw ClientError(message: 'login details incorrect.');
}
}
}
Validator:
class Validator {
String validateEmail(String value) {
if (value.isEmpty) return 'Email Should not be empty';
final RegExp emailRegEx = new RegExp(r'^\w+#[a-zA-Z_]+?\.[a-zA-Z]{2,3}$');
if (!emailRegEx.hasMatch(value)) return 'Your Email is invalid';
return null;
}
String validatePassword(String value) {
if (value.length < 4) return 'Password should be four characters or more';
return null;
}
}
AuhtScreen
import 'package:flutter/material.dart';
import 'package:mifity/blocs/auth_bloc.dart';
import 'package:mifity/blocs/bloc_provider.dart';
import 'package:mifity/helpers/validators.dart';
class AuhtScreen extends StatefulWidget {
#override
_AuhtScreenState createState() => _AuhtScreenState();
}
class _AuhtScreenState<StateClass> extends State<AuhtScreen> {
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
Validator validator = new Validator();
final formKey = GlobalKey<FormState>();
DecorationImage backgroundImage = new DecorationImage(
image: new ExactAssetImage('assets/images/bg_image.jpg'),
fit: BoxFit.cover,
);
#override
Widget build(BuildContext context) {
AuthBloc authBloc = BlocProvider.of(context).authBloc;
final Size screenSize = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
title: Text('Login'),
),
body: Builder(builder: (context) {
return SingleChildScrollView(
child: Container(
height: screenSize.height - AppBar().preferredSize.height,
padding: EdgeInsets.all(10.0),
alignment: Alignment.center,
decoration: BoxDecoration(
image: (backgroundImage != null) ? backgroundImage : null),
child: Center(
child: Form(
key: formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
style: TextStyle(color: Colors.white),
controller: emailController,
decoration: InputDecoration(
labelText: 'email',
labelStyle: TextStyle(color: Colors.grey)),
validator: validator.validateEmail,
),
TextFormField(
style: TextStyle(color: Colors.white),
controller: passwordController,
decoration: InputDecoration(
labelText: 'password',
labelStyle: TextStyle(color: Colors.grey)),
obscureText: true,
validator: validator.validatePassword,
),
SizedBox(
height: 20.0,
),
StreamBuilder<bool>(
initialData: false,
stream: authBloc.loading,
builder: (context, loadingSnapshot) {
return SizedBox(
width: double.infinity,
child: RaisedButton(
color: Colors.deepOrange,
textColor: Colors.white,
child: Text((loadingSnapshot.data)
? 'Login ...'
: 'Login'),
onPressed: () {
_submit(context, authBloc);
},
),
);
},
),
],
),
),
),
),
);
}));
}
_submit(context, AuthBloc authBloc) {
authBloc.emailChanged(emailController.text);
authBloc.passwordChanged(passwordController.text);
if (formKey.currentState.validate()) {
authBloc.submitLogin(context);
}
}
}
I'm an idiot!)
MyApp:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
child: MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new AuhtScreen(),
));
}
}