How to wait scheduled future with flutter_test? - flutter

My widget execute an asynchronous task and I want to check whether it is complete or not.
But it seems widget tester terminates the test before the task is executed. Whole code is here.
class MyHomePageState extends State<MyHomePage> {
#override
void initState() {
super.initState();
test();
}
Stream<int?> stream(Future<void> Function() worker) async* {
Stream<int?> work() async* {
await worker();
yield 3;
}
yield* work();
}
Future<void> sampleWorker() async {
throw Error();
}
Future<void> test() async {
print('start');
try {
await for (final v in stream(sampleWorker)) {
print(v);
}
print('point 1');
} catch (error, stack) {
print(stack);
}
print('done');
}
}
void main() {
testWidgets('Widget test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
await tester.pumpAndSettle(const Duration(milliseconds: 1000));
});
test('Plain test', () async {
await MyHomePageState().test();
});
}
I want both tests print stack trace, but only 'Plain test' shows expected behavior. The following is the log.
start
✓ Widget test
start
#0 MyHomePageState.sampleWorker (package:bug_report/main.dart:48:5)
#1 MyHomePageState.stream.work (package:bug_report/main.dart:40:19)
<asynchronous suspension>
#2 StackZoneSpecification._registerUnaryCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart)
<asynchronous suspension>
done
✓ Plain test
How to make first test prints stack trace? I should not await test() because it should be widget's internal task.

Try WidgetTester's runAsync function to await an arbitrary amount of time for an internal widget's async task to complete.

Related

Multiple testWidgets tests are not working in flutter integration test

I'm using google flutter integration driver to automate the flutter mobile app.
I want to write multiple widgetTests in a single main_test.dart file.
First test is running successfully, but when second test starts i see Test starting...(attached screenshot below) screen in emulator, but actually not moving forward to launch the app screen.
And finally, getting below error saying that Bad state: No element as screen is not loaded.!
Failure Details:
Failure in method: check value increments
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞═════════════════
The following StateError was thrown running a test:
Bad state: No element
When the exception was thrown, this was the stack:
#0 Iterable.single (dart:core/iterable.dart:654:25)
#1 WidgetController.element (package:flutter_test/src/controller.dart:114:30)
#2 WidgetController.ensureVisible (package:flutter_test/src/controller.dart:1210:73)
#3 main.<anonymous closure>.<anonymous closure> (file:///Users/tummalacharlajagadeesh/Desktop/Sparrow/synapse-cards-mobile/synapse-cards-mobile/integration_test/sanity_test.dart:87:20)
<asynchronous suspension>
Here is my integration test file main_test.dart:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:integration_test/integration_test.dart';
import 'package:sparrowmobile/flavor/uat.dart' as app;
void main() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
group('sanity test suite', () {
//runs before each test
setUp(() async {
app.main();
});
//runs after each test
tearDown(() async {
final getIt = GetIt.instance;
await getIt.reset();
});
testWidgets('sample test', (tester) async {
await tester.pumpAndSettle(const Duration(seconds: 5));
// ensure title is visible
await tester.ensureVisible(find.byKey(const ValueKey('startButton')));
await tester.tap(find.byKey(const ValueKey('startButton')));
await tester.pumpAndSettle();
expect(find.text('Home Screen'), findsOneWidget);
expect(find.text('Transactions'), findsOneWidget);
});
testWidgets('check value increments', (tester) async {
await tester.pumpAndSettle(const Duration(seconds: 10));
//ensure title is visible
await tester.ensureVisible(find.byKey(const ValueKey('startButton')));
await tester.tap(find.byKey(const ValueKey('startButton')));
await tester.pumpAndSettle();
});
});
}
Can anybody help here me to run multiple widgetTests in a single file?

Asynchronous method not running in proper order

I have these methods, for some reason fetchItems is being called first before initPosition, how come dart wont wait for it to finish and proceeds to the second method? I've added async/await but it still doesn't work. I've also checked my backend logs to confirm this. Am I doing something wrong?
Future<void> initPosition() async {
if (_latitude != null && _longitude != null) {
await Socket.updatePosition(
lat: 51,
lon: 17,);
}
}
Future<void> initMarkers() async {
await initPosition();
await Provider.of<Items>(context, listen: false)
.fetchItems();
}
void initMapState() async {
await getCurrentLocation().then((_) async {
await initMarkers();
setState(() {
_loaded = true;
});
});
}
#override
void initState() {
super.initState();
_location.enableBackgroundMode(enable: false);
WidgetsBinding.instance?.addPostFrameCallback((_) {
initMapState();
});
}
Future<void> fetchItems() async {
itemList = await repository.getItemList();
notifyListeners();
}
Working with multiple asynchronous functions inside Futures depends on whether one is finished or not, not every single one. For this, you can call the "whenComplete" method so you can assure that your future function have finished running. Like this:
For your initMarkers() function:
Future<void> initMarkers() async {
await initPosition().whenComplete((){
Provider.of<Items>(context, listen: false)
.fetchItems();
});
}
For your initMapState() function:
void initMapState() async {
await getCurrentLocation().whenComplete(() async {
await initMarkers().whenComplete((){
setState(() {
_loaded = true;
});
});
});
}
Keep in mind that, in your code, you are not working with the returning value of your getCurrentLocation() function, so instead of using the "then" method use the "whenComplete" method, assuring that you changed or returned your values with this function. Finally, for the initState(), make the function body with asynchronous:
#override
void initState() {
super.initState();
_location.enableBackgroundMode(enable: false);
WidgetsBinding.instance?.addPostFrameCallback((_) async {
initMapState();
});
}
This should work.

Issue after bloc migration - emit was called after an event handler completed normally

after I migrate to latest version of bloc, I am having an error.
Here's my code before migration:
SignInBloc(
{required this.authenticationRepository,
required this.userDataRepository})
: super(SignInInitialState());
SignInState get initialState => SignInInitialState();
#override
Stream<SignInState> mapEventToState(
SignInEvent event,
) async* {
if (event is SignInWithGoogle) {
yield* mapSignInWithGoogleToState();
}
if (event is UpdateLastLoginEvent) {
yield* mapUpdateLastLoginEventToState(event.uid, event.deviceID);
}
}
Stream<SignInState> mapSignInWithGoogleToState() async* {
yield SignInWithGoogleInProgressState();
try {
String res = await authenticationRepository.signInWithGoogle();
yield SignInWithGoogleCompletedState(res);
} catch (e) {
print(e);
yield SignInWithGoogleFailedState();
}
}
Stream<SignInState> mapUpdateLastLoginEventToState(
String uid, String deviceID) async* {
yield UpdateLastLoginInProgressState();
try {
String? res = await userDataRepository.lastLogin(uid, deviceID);
if (res != null) {
yield UpdateLastLoginCompletedState(res);
} else {
yield UpdateLastLoginFailedState();
}
} catch (e) {
print(e);
yield UpdateLastLoginFailedState();
}
}
Here's what I did for the code after migration. Although I am not sure if coding it with the try is still a good thing.
SignInBloc(
{required this.authenticationRepository,
required this.userDataRepository})
: super(SignInInitialState()) {
on<SignInEvent>((event, emit) async {
if (event is SignInWithGoogle) {
mapSignInWithGoogleToState(emit);
}
if (event is UpdateLastLoginEvent) {
mapUpdateLastLoginEventToState(emit, event.uid, event.deviceID);
}
});
}
Future<void> mapSignInWithGoogleToState(
Emitter<SignInState> emit,
) async {
emit(SignInWithGoogleInProgressState());
try {
String res = await authenticationRepository.signInWithGoogle();
emit(SignInWithGoogleCompletedState(res));
} catch (e) {
print(e);
emit(SignInWithGoogleFailedState());
}
}
Future<void> mapUpdateLastLoginEventToState(
Emitter<SignInState> emit,
String uid,
String deviceID,
) async {
emit(UpdateLastLoginInProgressState());
try {
String? res = await userDataRepository.lastLogin(uid, deviceID);
if (res != null) {
emit(UpdateLastLoginCompletedState(res));
} else {
emit(UpdateLastLoginFailedState());
}
} catch (e) {
print(e);
emit(UpdateLastLoginFailedState());
}
}
Here's what I see in the logs but if I try to implement future.whenComplete, it is having a syntax error. Please help. Thanks!
I/flutter (18083): emit was called after an event handler completed normally.
I/flutter (18083): This is usually due to an unawaited future in an event handler.
I/flutter (18083): Please make sure to await all asynchronous operations with event handlers
I/flutter (18083): and use emit.isDone after asynchronous operations before calling emit() to
I/flutter (18083): ensure the event handler has not completed.
I/flutter (18083):
I/flutter (18083): **BAD**
I/flutter (18083): on<Event>((event, emit) {
I/flutter (18083): future.whenComplete(() => emit(...));
I/flutter (18083): });
I/flutter (18083):
I/flutter (18083): **GOOD**
I/flutter (18083): on<Event>((event, emit) async {
I/flutter (18083): await future.whenComplete(() => emit(...));
I/flutter (18083): });
Please try to adjust it as follows:
SignInBloc(
{required this.authenticationRepository,
required this.userDataRepository})
: super(SignInInitialState()) {
// change this
/*on<SignInEvent>((event, emit) {
if (event is SignInWithGoogle) {
mapSignInWithGoogleToState(emit);
}
if (event is UpdateLastLoginEvent) {
mapUpdateLastLoginEventToState(emit, event.uid, event.deviceID);
}
});*/
// to this
on<SignInWithGoogle>(mapSignInWithGoogleToState);
on<UpdateLastLoginEvent>(mapUpdateLastLoginEventToState);
}
And also adjust your functions:
Future<void> mapSignInWithGoogleToState(
SignInWithGoogle event,
Emitter<SignInState> emit,
) async {
...
}
Future<void> mapUpdateLastLoginEventToState(
UpdateLastLoginEvent event,
Emitter<SignInState> emit,
) async {
...
}
Using try/catch approach is still fine!
Please let me know if it worked.

How to resolve emit was called after an event handler completed normally bloc error?

I am using flutter bloc to make download progress percentage displayed but I keep getting this problem. I figured the problem arises in the onDone method but I couldn't figure out how to fix it.
ERROR :
Exception has occurred.
_AssertionError ('package:bloc/src/bloc.dart': Failed assertion: line 137 pos 7: '!_isCompleted':
emit was called after an event handler completed normally.
This is usually due to an unawaited future in an event handler.
Please make sure to await all asynchronous operations with event handlers
and use emit.isDone after asynchronous operations before calling emit() to
ensure the event handler has not completed.
BAD
on<Event>((event, emit) {
future.whenComplete(() => emit(...));
});
GOOD
on<Event>((event, emit) async {
await future.whenComplete(() => emit(...));
});
)
CODE :
import 'package:bloc/bloc.dart';
import 'package:download_progress_with_bloc/downlaod_file.dart';
import 'package:download_progress_with_bloc/download_event.dart';
import 'package:download_progress_with_bloc/download_state.dart';
import 'package:download_progress_with_bloc/permission_handler.dart';
import 'package:download_progress_with_bloc/store_book_repo.dart';
import 'package:http/http.dart' as http;
class DownloadBloc extends Bloc<DownloadEvent, DownloadState> {
DownloadBloc({required this.storeBookRepo}) : super(DownloadInitial()) {
on<DownloadStarted>(onStarted);
on<DownloadProgressed>(onProgressed);
}
final StoreBookRepo storeBookRepo;
http.StreamedResponse? response;
// StreamController? _controller;
int received = 0;
List<int> bytes = [];
int totalSize = 0;
#override
Future<void> close() {
return super.close();
}
Future<void> onStarted(
DownloadStarted event, Emitter<DownloadState> emit) async {
try {
await PermissionHandler.requestStoragePermission();
response = await downloadFile();
totalSize = response!.contentLength ?? 0;
emit(DownloadInProgress(progress: received, totalSize: totalSize));
response?.stream.asBroadcastStream().listen((value) async {
received += value.length;
bytes.addAll(value);
add(DownloadProgressed(progress: received));
print('received value is $received');
}).onDone(
() async {
await storeBookRepo
.storePdf(
bytes.toString(),
bookTitle: 'bookTitle',
)
.then((value) => emit(DownloadCompleted()));
// emit(DownloadCompleted());
},
);
} catch (e) {
emit(DownlaodFailed(errorMessage: '$e'));
}
}
void onProgressed(DownloadProgressed event, Emitter<DownloadState> emit) {
emit(DownloadInProgress(progress: event.progress, totalSize: totalSize));
}
}
What if rewrite listen to await for like this?
Future<void> onStarted(
DownloadStarted event,
Emitter<DownloadState> emit,
) async {
try {
await PermissionHandler.requestStoragePermission();
response = await downloadFile();
totalSize = response!.contentLength ?? 0;
emit(DownloadInProgress(
progress: received,
totalSize: totalSize,
));
await for (final value in response?.stream) {
received += value.length;
bytes.addAll(value);
add(DownloadProgressed(progress: received));
print('received value is $received');
}
await storeBookRepo.storePdf(
bytes.toString(),
bookTitle: 'bookTitle',
);
emit(DownloadCompleted());
} catch (e) {
emit(DownlaodFailed(errorMessage: '$e'));
}
}
use async before on bloc and await for Future operations
on<NumberTriviaEvent>((event, emit) async {
await Future func(){//some time consuming operation like fetch data}
emit(YourState())
}
you should use async and await , its clear.
but why?
before complete run of each on() method, you can use emiteer. after that bloc will dispose or cancel that emitter.

Flutter: 'Future.wait' multiple async functions in parallel VS 'await' one at a time. <= different results

I recently learned of the fabulous way of waiting for multiple async functions to complete using Future.wait([asyncFuncOne(), asyncFunctwo()])
However, I noticed two different outcomes when running either of these blocks of code. One awaiting each function to finish, the other using Future.wait for parallel processing. What am I doing wrong?
Method 1:
await msm.initProfileData();
await msm.initActivityFeed();
await msm.getRecentlyActiveUsers();
await msm.getRecommendedUsers();
await msm.getGroups();
await msm.getFollowing();
await msm.getFollowers();
Method 2:
await Future.wait([
msm.getFollowing(),
msm.initProfileData(),
msm.initActivityFeed(),
msm.getRecentlyActiveUsers(),
msm.getRecommendedUsers(),
msm.getGroups(),
msm.getFollowers(),
]);
in Method 1, all the async functions complete before my apps home screen appears. In Method 2 the home screen appears before all the async functions complete.
Cheers and thanks in advance.
EDIT: Additional code example.
#override
void initState() {
super.initState();
googleSignIn.onCurrentUserChanged.listen((account) {
handleSignIn(account);
}, onError: (err) {
print('Error signing in: $err');
});
googleSignIn.signInSilently(suppressErrors: false).then((account) {
handleSignIn(account);
}).catchError((err) {
setState(() => _showSignIn = true);
print('Error signing in: $err');
});
}
handleSignIn(GoogleSignInAccount account) async {
if (account != null) {
await createUserInFirestore();
setState(() {
isAuth = true;
});
} else {
setState(() {
isAuth = false;
_showSignIn = true;
});
}
}
createUserInFirestore() async {
final GoogleSignInAccount user = googleSignIn.currentUser;
DocumentSnapshot doc = await usersRef.document(user.id).get();
//...
//do stuff
//...
await someFunc1(); //Method1
// await comeFunc2(); //Method2
//do more stuff
}
someFunc1() async {
msm.asyncfunc1();
msm.asyncfunc2();
}
someFunc2() async {
await Future.wait([
msm.asyncFunc1(),
msm.asyncFunc2(),
]);
}
#override
Widget build(BuildContext context) {
return isAuth ? buildAuthScreen() : buildUnAuthScreen();
}
Using Future.wait(List<Future>) will wait for all the async operations without sequence as mentioned in the docs. While using await consecutively, it'll wait for the first await async operation to finish before running the next await async operation. If you have a prerequisite output before running the next async operation, it's better to use await async in sequence instead.