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

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();
}

Related

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)

How to use a Variable from one class in another Class?

I want to use the ´bool DrawMode´ inside my ChangeDrawModeState class.
I need Something like ´GridState.drawMode´ but that does not work(drawMode is defined in GridState).
In the end I need to change the variable drawMode, if the RaisedButton gets Pressed. I'm not sure how to do this, cause the setState isnt working in the ChangeDrawModeState class as well. But isn't there a simple way to build a Button which turns a bool from True to false(or the other way around)?
class ChangeDrawMode extends StatefulWidget{
#override
ChangeDrawModeState createState(){
return new ChangeDrawModeState();
}
}
class ChangeDrawModeState<ChangeDrawMode>{
#override
Widget build(BuildContext context) {
return new RaisedButton(
child: new Text('Change Mode'),
textColor: Colors.white,
color: GridState.drawMode ? Colors.grey : Colors.blue,//HERE
onPressed: () =>setState(() => drawMode = !drawMode) //and HERE drawMode does not work
);
}
}
class Grid extends StatefulWidget {
#override
GridState createState() {
return new GridState();
}
}
class GridState extends State<Grid> {
bool drawMode = false;
final Set<int> selectedIndexes = Set<int>();
final key = GlobalKey();
final Set<_Foo> _trackTaped = Set<_Foo>();
_detectTapedItem(PointerEvent event) {
final RenderBox box = key.currentContext.findRenderObject();
final result = BoxHitTestResult();
Offset local = box.globalToLocal(event.position);
if (box.hitTest(result, position: local)) {
for (final hit in result.path) {
/// temporary variable so that the [is] allows access of [index]
final target = hit.target;
if (target is _Foo /*&& !_trackTaped.contains(target)*/) {
_trackTaped.add(target);
_selectIndex(target.index);
}
}
}
}
_selectIndex(int index) {
setState(
() {
if(selectedIndexes.contains(index)&&drawMode==false){
selectedIndexes.remove(index);
}
else if(drawMode==true){
selectedIndexes.add(index);
}
});
}
You can use InheritedWidget to update data and therefore widget state from any part of the app
Here is an example

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.

Why is data not being refreshed in StreamBuilder?

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.

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