问题
I am trying to fetch some data from the internet and show it int a list.
Following is my bloc
code
class StudentsBloc {
final _repository = Repository();
final _students = BehaviorSubject<StudentModel>();
final BehaviorSubject<bool> _showProgress = BehaviorSubject<bool>();
final BehaviorSubject<bool> _showNoInternetViews = BehaviorSubject<bool>();
Observable<StudentModel> get students => _students.stream;
Observable<bool> get showProgress => _showProgress.stream;
Observable<bool> get showNoInternetViews => _showNoInternetViews.stream;
//FetchStudent from my Api
fetchStudents(String disciplineId, String schoolId, String year_id,
String lastIndex) async {
final student = await _repository.fetchStudents(
disciplineId, schoolId, year_id, lastIndex);
_students.sink.add(student);
}
//Check to see if user has internet or not
isNetworkAvailable(String disciplineId, String schoolId, String year_id,
String lastIndex) async {
checkInternetConnection().then((isAvailable) {
if (isAvailable) {
fetchStudents(disciplineId, schoolId, year_id, lastIndex);
} else {
_students.sink.addError(NO_NETWORK_AVAILABLE);
}
});
}
Function(bool) get changeVisibilityOfProgress => _showProgress.sink.add;
Function(bool) get changeVisibilityOfNoInternetViews =>
_showNoInternetViews.sink.add;
dispose() {
_students.close();
_showProgress.close();
_showNoInternetViews.close();
}
}
Following is my main code to hide unide Widgets
Widget buildList(StudentsBloc bloc) {
return StreamBuilder(
stream: bloc.students,
builder: (context, AsyncSnapshot<StudentModel> snapshot) {
if (snapshot.hasError) {
bloc.changeVisibilityOfProgress(false);
bloc.changeVisibilityOfNoInternetViews(true);
return StreamBuilder(
stream: bloc.showNoInternetViews,
builder: (context, snapshot) {
bool showNoInternetView = snapshot.hasData ?? false;
return Visibility(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("No Network Available"),
RaisedButton(
onPressed: () {
fetchStudents();
},
child: Text("Retry"),
)
],
),
),
visible: showNoInternetView ? true : false,
);
},
);
}
if (snapshot.hasData) {
bloc.changeVisibilityOfProgress(false);
bloc.changeVisibilityOfNoInternetViews(false);
return Refresh(
year_id: "2",
schoolId: "1",
lastIndex: "0",
disciplineId: "1",
child: ListView.builder(
itemBuilder: (context, int index) {
return buildTile(
snapshot.data.toBuilder().data.studentData[index]);
},
itemCount: snapshot.data.toBuilder().data.studentData.length,
),
);
}
if (!snapshot.hasData) {
return StreamBuilder(
builder: (context, snapshot) {
bool showProgressIndicator = snapshot.data ?? false;
return Visibility(
child: Center(
child: CircularProgressIndicator(),
),
visible: showProgressIndicator ? true : false,
);
},
stream: bloc.showProgress,
);
}
},
);
}
The buildList
method is called in the body
of Scaffold
void fetchStudents() {
bloc?.changeVisibilityOfNoInternetViews(false);
bloc?.changeVisibilityOfProgress(true);
bloc?.isNetworkAvailable("1", "1", "2", "0");
}
Suppose the user has internet when app is opened then i am able to see circularprogressindicator
and then the list of data is visible
but suppose at the start when app is opened and the user does not have internet then i am showing the No Network Available Text and a button to retry,
now if the user has connected to the internet and then click on button to retry i am directly seeing the list of data after few seconds instead of the circularprogressindicator
.I am not able to understand why the NoInternetviews
are not hiding and progressindicator
is showing when retry button is clicked before showing list of data.
My stream is not getting updated on retry button called. Are there any caveats for StreamBuilder
within StreamBuilder
?
I tried changing the StreamBuilder
order as mentioned by @ivenxu in the answer but it still does not work.
Following are the links of attached code
https://drive.google.com/file/d/15Z8jXw1OpwTB1CxDS8sHz8jKyHhLwJp7/view?usp=sharing
https://drive.google.com/open?id=1gIXV20S1o5jYRnno_NADabuIj4w163fF
回答1:
in view layer you can use Visibility() widget and pass visible parameter true or false when load data from Internet. let's think about how to change the visible variable from bloc. The parent of Visibility() widget the StreamBuilder() to stream on changes data. for your case you need a PublishSubject inside your bloc to stream and add new data, and Observable to stream on this data on your widget.
let's show a snippet code to help you how you can implement it
The bloc contains PublishSubject and Observable to stream and add data
//this Subject allows sending data, error and done events to the listener
final PublishSubject<bool> _progressStateSubject = new PublishSubject();
//the listener are streaming on changes
Observable<bool> get progressStateStream => _progressStateSubject.stream;
//to change your progress state
void changeProgressState({bool state}) => _progressStateSubject.sink.add(state);
Here you can change your state of your progress
void callWebService() async {
//when you call your func to fetch your data change your state to true
changeProgressState(state: true);
.
.
.
if(response != null){
//when your call is done change the state to false
changeProgressState(state: false);
}
}
Your progress widget is
// Loading Widget
Widget _buildLoadingWidget(Bloc bloc) {
return StreamBuilder<bool>(
stream: bloc.progressStateStream,//streaming on changes
builder: (context, snapshot) {
return Visibility(
visible: snapshot.data ?? false,//calling when data changes
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Loading data from API...",
textDirection: TextDirection.ltr),
CircularProgressIndicator()
],
),
),
);
});
}
回答2:
It seems the cause is that CircularProgressIndicator is inside the students stream's update cycle. And the student steam only get next snapshot when the data returned from internet call in the case of retry.
Have a try on changing the order of stream builders, try putting the student streambuilder inside of the progress stream builder.
Widget buildList(StudentsBloc bloc) {
return StreamBuilder(
stream: bloc.showProgress,
builder: (context, snapshot) {
bool showProgressIndicator = snapshot.data ?? false;
if (!showProgressIndicator) {
StreamBuilder(
stream: bloc.students,
builder: (context, AsyncSnapshot<StudentModel> snapshot) {
....
//your original code without progress StreamBuilder
}
}
return Visibility(
child: Center(
child: CircularProgressIndicator(),
),
visible: showProgressIndicator ? true : false,
);
},
);
}
来源:https://stackoverflow.com/questions/54476006/bloc-pattern-to-hide-and-unhide-widgets