Why is data not being refreshed in StreamBuilder? - api

I am making a movie list application in Flutter and when I open the app I am displaying most-viewed movies list. When I click a button, I fetch new data and add it to the sink which is then sent to the StreamBuilder and should refresh the screen with new data.
But that doesn't happen. I cannot fathom why!
Here is my code for the Repository:
class MoviesRepository {
final _movieApiProvider = MovieApiProvider();
fetchAllMovies() async => _movieApiProvider.fetchMovieList();
fetchAllSimilarMovies(genreIdeas) async => await _movieApiProvider.fetchMoviesLikeThis(genreIdeas);
fetchTopRatedMovies() async => await _movieApiProvider.fetchTopRatedMovieList();
}
Here is my code for bloc:
class MoviesBloc {
final _moviesRepository = MoviesRepository();
final _moviesFetcher = BehaviorSubject<Result>();
Sink<Result> get allMovies => _fetcherController.sink;
final _fetcherController = StreamController<Result>();
Observable<Result> get newResults => _moviesFetcher.stream;
fetchAllMovies() async {
Result model = await _moviesRepository.fetchAllMovies();
allMovies.add(model);
}
fetchTopRatedMovies() async{
Result model = await _moviesRepository.fetchTopRatedMovies();
allMovies.add(model);
}
dispose() {
_moviesFetcher.close();
_fetcherController.close();
}
MoviesBloc() {
_fetcherController.stream.listen(_handle);
}
void _handle(Result event) {
_moviesFetcher.add(event);
}
}
final bloc = MoviesBloc();
UPDATE
And here is my code for the StreamBuilder:
class HomeScreenListState extends State<HomeScreenList> {
#override
void initState() {
super.initState();
print("init");
bloc.fetchAllMovies();
}
#override
void dispose() {
super.dispose();
bloc.dispose();
}
#override
Widget build(BuildContext context) {
return StreamBuilder<Result>(
builder: (context, snapshot) {
if (snapshot.hasData) {
print("new data is here");
return buildList(snapshot);
} else if (snapshot.hasError) {
return Text('Error!!');
}
return Center(
child: CircularProgressIndicator(),
);
},
stream: bloc.newResults,
);
}
}
And this is the button that triggers to get new data and add it to the sink:
child: RaisedButton(onPressed: () {
bloc.fetchTopRatedMovies();
},
This button fetches top-rated movies and add to the same sink. Now the StreamBuilder should pick up the new data as I think.
Where is the problem??

You can use Equatable alongside your BLoC implementation to manage state changes on your screen. Here's a guide that I suggest trying out.

Related

Flutter API calls with Future Builder returns error when future method called inside initState()

I was trying to call APIs that need to be loaded the first time the page is built. So I used FutureBuilder and written an initState call. Every page works fine but only on one page, I faced an issue with context. Then I understood about didChangeDependencies. Which is the correct way to call APIs from another class (that need to access the current widget context too).
Where should I call calculatorFuture = getParkingAreaList(); in initState() or didChangeDependencies().
String TAG = "CalculatorPage";
class CalculatorPage extends StatefulWidget {
const CalculatorPage({
Key key,
}) : super(key: key);
#override
_CalculatorPageState createState() => _CalculatorPageState();
}
class _CalculatorPageState extends State<CalculatorPage> {
Future calculatorFuture;
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
calculatorFuture = getParkingAreaList();
}
Future<bool> apiCallHere() async {
print('apiCallHere');
String lang = getCurrentLanguage(context);
print('going to call res');
var res = await HttpHandler.apiCallFromHttpHanlderClass(context);
if (res == null) {
print('res is null');
showToast(LanguageLocalization.of(context).getTranslatedValue('generic_failure_message'));
return false;
}
print(res);
if(res["STATUS_CODE"] == 1) {
print('apiCallHere status code = 1');
apiData = res['someData'];
return true;
}
else {
print('apiCallHere status code !=1');
return false;
}
}
#override
Widget build(BuildContext context) {
print(TAG);
return Scaffold(
appBar: commonAppBar(context: context, title: 'calculator'),
body: FutureBuilder(
future: calculatorFuture,
builder: (context, snapshot) {
print(snapshot);
if (snapshot.hasError || (snapshot.hasData && !snapshot.data)) {
print('has error');
print(snapshot.hasError ? snapshot.error : "unable to load data");
return unableToLoadView(context);
} else if (snapshot.hasData && snapshot.data) {
print('completed future');
return Container(
margin: commonPagePadding,
child: ListView(
children: <Widget>[
//some widget that deals with apiData
],
)
);
} else {
print('loading');
return showLoaderWidget(context);
}
},
)
);
}
}
you can call future function in didchangedependenci changing like that
#override
Future <void> didChangeDependencies() async{
super.didChangeDependencies();
calculatorFuture = await getParkingAreaList();
}
If you want to call the API once when a page loads up just place the future inside initState like the example below.
#override
void initState() {
super.initState();
calculatorFuture = getParkingAreaList();
}

Unhandled Exception: type 'String' is not a subtype of type 'Future<String>' in type cast

I am gettting this error ,can anyone help me to sort out this error
static Future<String> get_video_lecture_subject(int schoolId,int classroom) async {
var body;
body = jsonEncode({
"school_id": schoolId,
"classroom": classroom,
});
final response = await http.post(
'https://b5c4tdo0hd.execute-api.ap-south-1.amazonaws.com/testing/get-video-lecture-subjects',
headers: {"Content-Type": "application/json"},
body: body,
);
print(response.body.toString());
return response.body.toString();
}
i have used above function in getpref() function
getpref() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int classNo = int.parse(prefs.getString("class")[0]);
int schoolId = prefs.getInt("school_id");
print("hello");
response=(await ApiService.get_video_lecture_subject(schoolId, classNo)) as Future<String> ;
print(response);
}
The expression:
(await ApiService.get_video_lecture_subject(schoolId, classNo)) as Future<String>
calls your method get_video_lecture_subject. That returns a Future<String>.
You then await that future, which results in a String.
Then you try to cast that String to Future<String>. That fails because a string is not a future.
Try removing the as Future<String>.
Check out this sample example and let me know if it works
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
getPrefs();
runApp(MyApp());
}
void getPrefs() async {
String value = await yourfunction();
print(value);
}
Future<String> yourfunction() {
final c = new Completer<String>();
// Below is the sample example you can do your api calling here
Timer(Duration(seconds: 1), () {
c.complete("you should see me second");
});
// Process your thigs above and send the string via the complete method.
// in you case it will be c.complete(response.body);
return c.future;
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child:
/* FutureBuilder<String>(
future: yourfunction(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data);
}
return CircularProgressIndicator();
}), */
Text('')),
));
}
}
Replace the line with this
final res = await ApiService.get_video_lecture_subject(schoolId, classNo);
Update: Change the variable name (maybe it's type was initialized as Future)

Fetching data simultaneously in flutter

I need to a clarification on this,I create a function with sharepreferrence which contain asyn, And in this same function am trying to fetch data from the server, And Am using FutureBuilder to display the output
BUT what i observe is that the data keep fetching simultaneously,
What can it be the Problem. Here is the code
My Function for fetching Data
List<TransactionsModel> parseTransactons (String responseBody) {
Map data = json.decode(responseBody);
var output = data['transactions']; //returns a List of Maps
final castingtoMAP = output.cast<Map<String, dynamic>>();
return castingtoMAP.map<TransactionsModel>((json) =>
TransactionsModel.fromJson(json)).toList();
}
Future<List<TransactionsModel>> fetchTransactions(http.Client client) async {
final SharedPreferences prefs = await
SharedPreferences.getInstance();
String email = prefs.getString('Email');
String session = prefs.getString('session');
var map = new Map<String, String>();
map["email"] = email;
map["sesscode"] = session;
var response = await http.post(new API().Transaction, body: map);
print('*********Response from Transaction API***** $response');
return parseTransactons(response.body);
}
Displaying Data
FutureBuilder<List<TransactionsModel>>(
future: fetchTransactions(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError)
return MyFunctions().showToast(snapshot.error);
return snapshot.hasData
? TransactionCardWidget(snapshot.data)
: Center(child: CircularProgressIndicator());
},
),
If your widget is Stateless or is Stateful and you are calling setState a new instance of Future<List<TransactionsModel>> is being created by calling fetchTransactions, thus making a new request. You need to save the Future that the function returns in the State of a StatefulWidget and then call fetchTransactions in the initState function.
class .... extends State<..> {
Future<List<TransactionsModel>> transactionsFuture;
void initState() {
transactionsFuture = fetchTransactions(http.Client());
}
Widget build(BuildContext context){
...
FutureBuilder<List<TransactionsModel>>(
future: transactionsFuture,
builder: (context, snapshot) {
...
}
}

Flutter: how to mock Bloc

I would like to mock my Bloc in order to test my view.
For example, this is my Bloc:
class SearchBloc extends Bloc<SearchEvent, SearchState> {
#override
// TODO: implement initialState
SearchState get initialState => SearchStateUninitialized();
#override
Stream<SearchState> mapEventToState(SearchState currentState, SearchEvent event) async* {
if (event is UserWrites) {
yield (SearchStateInitialized.success(objects);
}
}
}
And this is the view:
class _SearchViewState extends State<SearchView> {
final TextEditingController _filterController = new TextEditingController();
#override
void initState() {
_filterController.addListener(() {
widget._searchBloc.dispatch(FetchByName(_filterController.text));
}
}
TextField buildAppBarTitle(BuildContext context) {
return new TextField(
key: Key("AppBarTextField"),
controller: _filterController,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: buildAppBarTitle(context),),
body: buildBlocBuilder(),
);
}
BlocBuilder<SearchEvent, SearchState> buildBlocBuilder() {
return BlocBuilder(
bloc: widget._searchBloc,
builder: (context, state) {
if (state is SearchStateUninitialized) {
return Container(
key: Key("EmptyContainer"),
);
}
return buildInitializedView(state, context);
}
});
buildInitializedView(SearchStateInitialized state, BuildContext context) {
if (state.objects.isEmpty) {
return Center(child: Text("Nothing found"),);
} else {
return buildListOfCards();
}
}
}
Now, this is my test:
testWidgets('Should find a card when the user searches for something', (WidgetTester tester) async {
_searchView = new SearchView(_searchBloc);
when(mockService.find( name: "a")).thenAnswer((_) =>
[objects]);
await tester.pumpWidget(generateApp(_searchView));
await tester.enterText(find.byKey(Key("searchBar")), "a");
await tester.pump();
expect(find.byType(Card), findsOneWidget);
});
}
As you can see, I just want to test that, when the user writes something in the search, and the object he's looking for exists, a card should be shown.
If I understood correctly, you are mocking some service that is used by the searchBloc. I personally try to design the app in a way that the app only depends on a bloc and the bloc may depend on some other services. Then when I would like to make a widget test, I only need to mock the bloc. You can use bloc_test package for that.
There is this example on the bloc_test page for stubbing a counterBloc:
// Create a mock instance
final counterBloc = MockCounterBloc();
// Stub the bloc `Stream`
whenListen(counterBloc, Stream.fromIterable([0, 1, 2, 3]));
however, I often do not need to stub the bloc stream and it is enough to emit the state, like this
when(counterBloc.state).thenAnswer((_) => CounterState(456));
Hope this helps.
Have a look at a post from David Anaya which deal with Unit Testing with “Bloc” and mockito.
The last version of his example is here
Sometimes widgets require a little time to build. Try with:
await tester.pumpWidget(generateApp(_searchView));
await tester.enterText(find.byKey(Key("searchBar")), "a");
await tester.pump(Duration(seconds: 1));
expect(find.byType(Card), findsOneWidget);
To mock the bloc, you can use the bloc_test package
Also, you may watch this tutorial which covers bloc testing include mock bloc very nice.

Flutter: how to mock a stream

I´m using the bloc pattern and now I came to test the UI. My question is: how to mock streams?
This is the code that I have:
I give to the RootPage class an optional mockBloc value and I will set it to the actual _bloc if this mockBloc is not null
class RootPage extends StatefulWidget {
final loggedOut;
final mockBlock;
RootPage(this.loggedOut, {this.mockBlock});
#override
_RootPageState createState() => _RootPageState();
}
class _RootPageState extends State<RootPage> {
LoginBloc _bloc;
#override
void initState() {
super.initState();
if (widget.mockBlock != null)
_bloc = widget.mockBlock;
else
_bloc = new LoginBloc();
if (widget.loggedOut == false)
_bloc.startLoad.add(null);
}
...
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _bloc.load,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: <Widget>
...
This is what I´ve tried:
testWidgets('MyWidget', (WidgetTester tester) async {
MockBloc mockBloc = new MockBloc();
MockTokenApi mockTokenApi = new MockTokenApi();
await tester.pumpWidget(new MaterialApp(
home: RootPage(false, mockBlock: mockBloc)));
when(mockBloc.startLoad.add(null)).thenReturn(mockBloc.insertLoadStatus.add(SettingsStatus.set)); //this will give in output the stream that the StreamBuilder is listening to (_bloc.load)
});
await tester.pump();
expect(find.text("Root"), findsOneWidget);
});
The result that I achieve is always to get:
The method 'add' was called on null
when _bloc.startLoad.add(null) is called
You can create a mock Stream by creating a mock class using mockito. Here's a sample on how one can be implemented.
import 'package:mockito/mockito.dart';
class MockStream extends Mock implements Stream<int> {}
void main() async {
var stream = MockStream();
when(stream.first).thenAnswer((_) => Future.value(7));
print(await stream.first);
when(stream.listen(any)).thenAnswer((Invocation invocation) {
var callback = invocation.positionalArguments.single;
callback(1);
callback(2);
callback(3);
});
stream.listen((e) async => print(await e));
}
Since the advent of null safety, you can do this as follows:
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'test_test.mocks.dart';
#GenerateMocks([Stream])
void main() {
test('foo', () async {
var stream = MockStream();
Stream<int> streamFunc() async* {
yield 1;
yield 2;
yield 3;
}
when(stream.listen(
any,
onError: anyNamed('onError'),
onDone: anyNamed('onDone'),
cancelOnError: anyNamed('cancelOnError'),
)).thenAnswer((inv) {
var onData = inv.positionalArguments.single;
var onError = inv.namedArguments[#onError];
var onDone = inv.namedArguments[#onDone];
var cancelOnError = inv.namedArguments[#cancelOnError];
return streamFunc().listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
});
stream.listen((e) async => print(await e));
});
}
source