implementing simple rxDart with Bloc don't get result - flutter

from this link on my web server as
http://instamaker.ir/api/v1/getPersons
i'm trying to get result and printing avatar from that result, unfortunately my implementation with rxDart and Bloc don't get result from this response and i don't get any error
server response this simplified result:
{
"active": 1,
"name": "my name",
"email": " 3 ",
"loginType": " 3 ",
"mobile_number": " 3 ",
...
"api_token": "1yK3PvAsBA6r",
"created_at": "2019-02-12 19:06:34",
"updated_at": "2019-02-12 19:06:34"
}
main.dart file: (click on button to get result from server)
StreamBuilder(
stream: bloc.login,
builder: (context,
AsyncSnapshot<UserInfo>
snapshot) {
if (snapshot.hasData) {
parseResponse(snapshot);
}
},
);
void parseResponse(AsyncSnapshot<UserInfo> snapshot) {
debugPrint(snapshot.data.avatar);
}
LoginBlock class:
class LoginBlock{
final _repository = Repository();
final _login_fetcher = PublishSubject<UserInfo>();
Observable<UserInfo> get login=>_login_fetcher.stream;
fetchLogin() async{
UserInfo userInfo = await _repository.userInfo();
_login_fetcher.sink.add(userInfo);
}
dispose(){
_login_fetcher.close();
}
}
final bloc = LoginBlock();
Repository class:
class Repository {
final userInformation = InstagramApiProviders();
Future<UserInfo> userInfo() => userInformation.checkUserLogin();
}
my model:
class UserInfo {
int _active;
String _name;
...
UserInfo.fromJsonMap(Map<String, dynamic> map)
: _active = map["active"],
_name = map["name"],
...
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['active'] = _active;
data['name'] = _name;
...
return data;
}
//GETTERS
}
BaseUrl class:
class BaseUrl {
static const url = 'http://instamaker.ir';
}
and then InstagramApiProviders class:
class InstagramApiProviders {
Client client = Client();
Future<UserInfo> checkUserLogin() async {
final response = await client.get(BaseUrl.url+'/api/v1/getPersons');
print("entered "+BaseUrl.url+'/api/v1/getPersons');
if (response.statusCode == 200) {
return UserInfo.fromJsonMap(json.decode(response.body));
} else
throw Exception('Failed to load');
}
}

Well the answer here is part of the test that I make to get this done. I can put my all test here but I think that the problem cause was because as StreamBuilder is a widget his builder method callback is only called when the widget is in flutter widget tree. As in your sample you're just creating a StreamBuilder the builder method will never be called bacause this widget isn't in widget tree.
As advice first test your code changing only UI layer... do somenthing like:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.assessment), onPressed: () => loginBlock.fetchLogin()),
],
),
body: StreamBuilder<UserInfo>(
stream: loginBlock.login,
builder: (context, snapshot){
if (snapshot.hasData){
parseResponse(snapshot);
return Text('user: ${snapshot.data.name} ');
}
if (snapshot.hasError)
return Text('${snapshot.error}');
else return Text('There is no data');
},
),
);
Here we're putting the StreamBuilder in widget tree so the builder callback is called and maybe you will see the results. If it fails, please comment that I update my answer with my full test code with this working.
Updating the answer with sources that I made tests.
Basic model
class UserInfo {
int _active;
String name;
UserInfo.fromJsonMap(Map<String, dynamic> map) {
_active = map["active"];
name = map["name"];
}
Map<String, dynamic> toJson() => {
'active' : _active,
'name' : name,
};
}
The provider class
class InstagramApiProviders {
Future<UserInfo> checkUserLogin() async {
UserInfo info;
try {
http.Response resp = await http.get("http://instamaker.ir/api/v1/getPersons");
if (resp.statusCode == 200){
print('get response');
print( resp.body );
info = UserInfo.fromJsonMap( Map.from( json.decode(resp.body ) ));
}
}
catch (ex) {
throw ex;
}
print('returning $info');
return info;
}
}
Repository
class Repository {
final userInformation = InstagramApiProviders();
Future<UserInfo> userInfo() => userInformation.checkUserLogin().then((user) => user);
}
BLoC class
class LoginBlock{
final _repository = Repository();
final _login_fetcher = PublishSubject<UserInfo>();
Observable<UserInfo> get login=>_login_fetcher.stream;
fetchLogin() async {
UserInfo info = await _repository.userInfo();
_login_fetcher.sink.add(info);
}
dispose(){
_login_fetcher.close();
}
}
Widget UI
This starts showing There is no data message but when you hit appBar button wait a little and then the data is fetched and updates the UI.
class WidgetToShowData extends StatelessWidget {
final LoginBlock bloc = LoginBlock();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.assessment), onPressed: () => loginBlock.fetchLogin()),
],
),
body: StreamBuilder<UserInfo>(
stream: loginBlock.login,
builder: (context, snapshot){
if (snapshot.hasData){
parseResponse(snapshot);
return Text('user: ${snapshot.data.name} ');
}
if (snapshot.hasError)
return Text('${snapshot.error}');
else return Text('There is no data');
},
),
);
}
void parseResponse(AsyncSnapshot<UserInfo> snapshot) {
debugPrint(snapshot.data.name);
}
}

Related

LateInitializationError: Field 'myfuture' has not been initialized

The issue that I am facing is in future builder in flutter.When opening the page first time the data is loaded successfully but when I go to a different page and then return to the same page it throws an error LateInitializationError: Field 'myfuture' has not been initialized.
Hence if you could please help me resolve this issue.
Please find below the code and let me know if any further information is required from my end.
view.dart
late final Future myfuture;
#override
void initState() {
print('init started'); // on opening second time the process gets stuck here with the above error message
if (Provider.of<FilterOptionProvider>(context, listen: false)
.initialList
.isEmpty) {
myfuture = Provider.of<FilterOptionProvider>(context, listen: false)
.readfilters(checkfilters);
}
super.initState();
}
Widget _buildList() {
final notificationData =
Provider.of<FilterOptionProvider>(context, listen: true);
final ndata = notificationData.initialList;
return FutureBuilder(
future: myfuture,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: LinearProgressIndicator());
} else if (snapshot.error != null) {
return const Center(
child: Text('An error occured'),
);
} else {
final notificationData =
Provider.of<FilterOptionProvider>(context, listen: true);
final ndata = notificationData.initialList;
provider.dart
Future<void> readfilters(Map<String, dynamic> queryPam) async {
Map<String, String> headers = {
"Content-Type": "charset=utf-8",
"Content-type": "application/json"
};
Just init the empty future in else,
if (Provider.of<Filter...) {
...
}
else {
myFuture = Future(() {});
}

Riverpod FutureProvider keeps on firiging again and again

I am using Riverpod's FutureProvider with family. The FutureProvider keeps on running again and again. It shows the loading dialog only. Also the hot reload stops working. FutureProvider is working fine without family. Please help in finding what's wrong.
final ephemerisProvider =
Provider((ref) => ApiService("https://localhost"));
final ephemerisFutureProvider = FutureProvider.family
.autoDispose<EpheModel, Map<String, dynamic>>((ref, data) async {
var response = await ref.read(ephemerisProvider).getData(data);
print(EpheModel.fromJSON(response));
return EpheModel.fromJSON(response);
});
class Kundlis extends ConsumerWidget {
static const routeName = "/kundlis";
#override
Widget build(BuildContext context, ScopedReader watch) {
final AsyncValue<EpheModel> kundlis = watch(ephemerisFutureProvider({}));
return Scaffold(
appBar: AppBar(
title: Text("Kundlis"),
),
drawer: AppDrawer(),
body: kundlis.when(
data: (kundli) => Center(child: Text(kundli.toString())),
loading: () => ProgressDialog(message: "Fetching Details..."),
error: (message, st) =>
CustomSnackBar.buildErrorSnackbar(context, '$message')));
}
}
class ApiService {
final String url;
ApiService(this.url);
Future<Map<String, dynamic>> getData(Map<String, dynamic> data) async {
try {
http.Response response = await http.post(url + "/ephe",
headers: <String, String>{'Content-Type': 'application/json'},
body: jsonEncode(data));
if (response.statusCode == 200) {
return data;
} else {
throw Exception("Error Fetching Details");
}
} on SocketException {
throw Exception("No Internet Connection");
} on HttpException {
throw Exception("Error Fetching Details");
}
}
}
{} != {}. Because of .family, you are creating a completely new provider every time you call watch(ephemerisFutureProvider({})). To select a previously-built provider via family, you must pass an identical value. And {} is never identical to {}, guaranteed. :)

Pull-to-refresh in Flutter don't work when no internet connection

I have a class that displays in a list view some JSON data (events) that I get with an API request and save them to the storage of the device so to not be downloaded every time, UNLESS the user makes a Pull-to-refresh operation so to download news events.
In case during the operation of download there is no internet connection the app display "Impossible to download the events list: check your internet connection!".
So I aspect that if it is the first time the user opens the app, it should download the events or show in case of internet connection missing the message mentioned above (or that there are no events in case the length of the events array downloaded == 0). If it is not the first time show the list of the events previously downloaded and saved.
My problem is that if, for example, I have internet turned off and after I turned on, the pull to refresh doesn't work, instead when I have the list downloaded I can make a pull to refresh operation.
This is my code:
class EventDetails {
String data;
int id;
String name;
String description;
String applicationStatus;
String applicationStarts;
String applicationEnd;
String starts;
String ends;
int fee;
EventDetails({
this.data,
this.id,
this.name,
this.description,
this.applicationStatus,
this.applicationStarts,
this.applicationEnd,
this.starts,
this.ends,
this.fee,
});
EventDetails.fromJson(Map<String, dynamic> json) {
data = json['data'];
id = json['id'];
name = json['name'];
description = json['description'];
applicationStatus = json['application_status'];
applicationStarts = json['application_starts'];
applicationEnd = json['application_ends'];
starts = json['starts'];
ends = json['ends'];
fee = json['fee'];
}
}
class EventsListView extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _EventListState();
}
}
class _EventListState extends State<EventsListView> {
List<EventDetails> list;
Storage storage = Storage();
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _getData,
child: FutureBuilder<List<EventDetails>>(
future: loadEvents(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<EventDetails> data = snapshot.data;
if (data.length == 0) {
return Text("No events found",
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20,
));
} else {
return _eventsListView(data);
}
} else if (snapshot.hasError) {
if (snapshot.error.runtimeType.toString() == "SocketException") {
return Text(
"Impossible to download the events list: check your internet connection!",
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20,
));
} else {
return Text("${snapshot.error}");
}
}
return CircularProgressIndicator();
},
),
);
}
Future<List<EventDetails>> loadEvents() async {
String content = await storage.readList();
if (content != 'no file available') {
list = getListFromData(content, list);
}
if ((list != null) && (list.length != 0)) {
print('not empty');
return list;
} else {
return await downloadEvents(list, storage);
}
}
Future<List<EventDetails>> downloadEvents(
List<EventDetails> list, Storage storage) async {
String url = "https://myurl";
final response = await http.get(url);
if (response.statusCode == 200) {
String responseResult = response.body;
list = getListFromData(responseResult, list);
storage.writeList(response.body);
return list;
} else {
throw Exception('Failed to load events from API');
}
}
List<EventDetails> getListFromData(String response, List<EventDetails> list) {
Map<String, dynamic> map = json.decode(response);
List<dynamic> jsonResponse = map["data"];
list = jsonResponse.map((job) => new EventDetails.fromJson(job)).toList();
return list;
}
ListView _eventsListView(data) {
return ListView.separated(
itemCount: data.length,
separatorBuilder: (context, index) => Divider(
color: const Color(0xFFCCCCCC),
),
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
child: _tile(data[index].name),
onTap: () {
Navigator.pushNamed(
context,
SingleEvent.routeName,
arguments: ScreenArguments(
data[index].name,
data[index].description,
data[index].starts,
data[index].ends,
),
);
});
});
}
Future<void> _getData() async {
setState(() {
downloadEvents(list,storage);
});
}
#override
void initState() {
super.initState();
loadEvents();
}
ListTile _tile(String title) => ListTile(
title: Text(title,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20,
)),
);
}
I am really new in Flutter, what I am doing wrong?
FutureBuilder will not refresh once the future is evaluated. If you want the pull to refresh to work, you could just store the list data as a state of the widget and render different UI based on the state.
In addition to that, RefreshIndicator will not work if the child is not scrollable. Instead returning plain Text widget when there is no data, return SingleChildScrollView with a text inside so that you have a scrollable inside your RefreshIndicator.
Here is an example:
class EventsListView extends StatefulWidget {
#override
_EventsListViewState createState() => _EventsListViewState();
}
class _EventsListViewState extends State<EventsListView> {
List list;
Storage storage = Storage();
String errorMessage;
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: downloadEvents,
child: listWidget(),
);
}
Widget listWidget() {
if (list != null) {
return ListView(); // here you would return the list view with contents in it
} else {
return SingleChildScrollView(child: Text('noData')); // You need to return a scrollable widget for the refresh to work.
}
}
Future<void> loadEvents() async {
String content = await storage.readList();
if (content != 'no file available') {
list = getListFromData(content, list);
}
if ((list != null) && (list.length != 0)) {
print('not empty');
errorMessage = null;
setState(() {});
} else {
await downloadEvents();
}
}
Future<void> downloadEvents() async {
String url = "https://myurl";
final response = await http.get(url);
if (response.statusCode == 200) {
String responseResult = response.body;
list = getListFromData(responseResult, list);
storage.writeList(response.body);
errorMessage = null;
setState(() {});
} else {
setState(() {
errorMessage =
'Error occured'; // here, you would actually add more if, else statements to show better error message
});
throw Exception('Failed to load events from API');
}
}
List<EventDetails> getListFromData(String response, List<EventDetails> list) {
Map<String, dynamic> map = json.decode(response);
List<dynamic> jsonResponse = map["data"];
list = jsonResponse.map((job) => new EventDetails.fromJson(job)).toList();
return list;
}
#override
void initState() {
super.initState();
loadEvents();
}
}

Flutter: StreamBuilder Snapshot -- No Data

I am just learning Flutter and am trying to use a StreamBuilder to display a Login / Register page if the user is logged out, or a Profile page if the user is logged in. My code is below:
Auth Service:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class AuthUser {
AuthUser({#required this.uid, #required this.email});
final String uid;
final String email;
}
abstract class AuthBase {
Future<AuthUser> currentUser();
Future<AuthUser> signIn({String email, String pw});
Future<AuthUser> registerUser({String email, String pw});
Stream<AuthUser> get onAuthStateChanged;
Future<void> signOut();
}
class Auth implements AuthBase {
final _firebaseAuth = FirebaseAuth.instance;
AuthUser _userFromFirebase(FirebaseUser user) {
if (user != null) {
return AuthUser(uid: user.uid, email: user.email);
} else {
return null;
}
}
#override
Stream<AuthUser> get onAuthStateChanged {
return _firebaseAuth.onAuthStateChanged.map(_userFromFirebase);
}
#override
Future<AuthUser> currentUser() async {
final user = await _firebaseAuth.currentUser();
return _userFromFirebase(user);
}
#override
Future<AuthUser> signIn({String email, String pw}) async {
final authResult = await _firebaseAuth.signInWithEmailAndPassword(email: email, password: pw);
return _userFromFirebase(authResult.user);
}
#override
Future<AuthUser> registerUser({String email, String pw}) async {
final authResult = await _firebaseAuth.createUserWithEmailAndPassword(email: email, password: pw);
return _userFromFirebase(authResult.user);
}
#override
Future<void> signOut() async {
await _firebaseAuth.signOut();
}
}
StreamBuilder:
class WelcomeScreen extends StatelessWidget {
WelcomeScreen({#required this.auth});
static const String id = '/';
final AuthBase auth;
#override
Widget build(BuildContext context) {
return StreamBuilder<AuthUser>(
stream: auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.hasData) {
AuthUser user = snapshot.data;
if (user == null) {
return displayLoginOrRegPage(context);
} else {
return ProjectScreen(
user: user,
auth: auth,
);
}
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
It was my understanding the stream would begin emitting 'null' once it was initialized, and would continue doing so until it fired off an Auth state change...
But the snapshot continually reports "No Data" and thus my code is stuck on the CircularProgressIndicator.
BTW, if I display the log-in screen in place of the progress indicator, the code works. So I'm clearly not understanding the whole stream initialization process.
Can somebody kindly explain to me where I have gone wrong here? Thanks a million in advance.
As you mentioned, when stream initialises it emits null, but when the user is not logged in, it still emits null, which stream considers as no data i.e null that's the reason for the error.
You can use Streambuilder's connection state to differentiate between no user null and null after initialisation.
I hope following code helps you.
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.data == null) {
return displayLoginOrRegPage(context);
} else {
AuthUser user = snapshot.data;
return ProjectScreen(
user: user,
auth: auth,
);
}
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
Per #VirenVVarasadiya, it was definitely a case of checking the ConnectionState. Here is the final working code. Thank you!
#override
Widget build(BuildContext context) {
return StreamBuilder<AuthUser>(
stream: auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.data != null) {
AuthUser user = snapshot.data;
return ProjectScreen(
user: user,
auth: auth,
);
} else {
return displayLoginOrRegPage(context);
}
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
Try to change WelcomeScreen to state full Widget.

How to do stream builder to get data from bloc in flutter

I am new in BLOC and I am trying to read respond from api.. but whenever I call stream builder... my widget always stops in wait... here is my code
here is api provider file
class Provider {
final _url = '...';
Future<List<LoginRespon>> login(a, b) async {
List<LoginRespon> datalogin = [];
try {
bool trustSelfSigned = true;
HttpClient httpClient = new HttpClient()
..badCertificateCallback =
((X509Certificate cert, String host, int port) =>
trustSelfSigned);
IOClient http = new IOClient(httpClient);
final response = await http.post(_url,
headers: {
HttpHeaders.contentTypeHeader: 'application/json',
},
body: json.encode({
"aa": a,
"bb": b,
}));
Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson["status"] == "200") {
datalogin.add(LoginRespon(
status: responseJson['status'],
data: Data(
name: responseJson['data']['name'],
position: responseJson['data']['pos'])));
return datalogin;
} else {
print("ppp");
}
} on Exception {
rethrow;
}
return datalogin;
}
}
and here is for stream builder
isClick
? StreamBuilder(
stream: bloc.login(),
builder: (context, snapshot) {
if (snapshot.hasData) {
print(snapshot.data);
return Text("success");
} else if (snapshot.hasError) {
return Text(
snapshot.error.toString());
}
return Text("wait..");
},
)
: FlatButton(
child: Text("Login"),
onPressed: () {
setState(() {
isClick = true;
});
},
),
is there a way so that I can call print(snapshot.data) inside if (snapshot.hasData)
You need to pass argument which required in method otherwise it will not successfully responce (200) and it will throw error.