Writing test for bottom sheet in flutter - flutter

The test is to check that the showModelBottomSheet move along the keyboard or not, showModelBottomSheet must show above the keyboard.
I write this code but failed.
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/basic.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('To Check the Position of showModelBottomSheet on keyboard popup', () {
late BuildContext savedContext;
Widget _buildTest(MediaQueryData data, Key buttonKey, Key showModelBottomSheetKey){
return MaterialApp(
home: Builder(
builder: (BuildContext context) {
return FlatButton(
key: buttonKey,
onPressed: () {
print(MediaQuery.of(context).size);
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
savedContext = context;
return Container(
key: showModelBottomSheetKey,
child: const TextField(),
);
},
);
},
child: const Text('Press'),
);
},
),
);
}
Future<void> _runFloatTests(WidgetTester tester, {required Rect defaultRect}) async {
const double keyboardHeight = 200.0;
const double viewPadding = 50.0;
final GlobalKey buttonKey = GlobalKey();
final GlobalKey showModelBottomSheetKey = GlobalKey();
// Default without Keyboard
await tester.pumpWidget(_buildTest(
const MediaQueryData(
viewPadding: EdgeInsets.only(bottom: viewPadding)
),
buttonKey,
showModelBottomSheetKey));
await tester.tap(find.byKey(buttonKey));
await tester.pumpAndSettle();
print();
print(tester.getRect(find.byKey(showModelBottomSheetKey)));
print(rectMoreOrLessEquals(defaultRect));
print('-------------------');
expect(
tester.getRect(find.byKey(showModelBottomSheetKey)),
rectMoreOrLessEquals(defaultRect),
);
// Present keyboard and check position, should change
await tester.pumpWidget(_buildTest(
const MediaQueryData(
viewPadding: EdgeInsets.only(bottom: viewPadding),
viewInsets: EdgeInsets.only(bottom: keyboardHeight),
),
buttonKey,
showModelBottomSheetKey
));
print(tester.getRect(find.byKey(showModelBottomSheetKey)));
print(rectMoreOrLessEquals(defaultRect.translate(
0.0,
-keyboardHeight,
)));
print('-------------------');
expect(
tester.getRect(find.byKey(showModelBottomSheetKey)),
rectMoreOrLessEquals(defaultRect.translate(
0.0,
-keyboardHeight,
)),
);
}
testWidgets('',
(WidgetTester tester) async {
const Rect defaultRect = Rect.fromLTRB(0.0, 552.0, 800.0, 600.0);
await _runFloatTests(tester, defaultRect: defaultRect);
await tester.pump();
});
});
}

Related

Flutter Hive putAt() cannot run if click done button on keyboard

I try to make a flutter app using hive as a database.
There is a Container to show balance and if the user click the container a form dialog will show to change the balance value.
After change the value on TextFormField if user click submit everything will work fine, but if the user click the done button on the keyboard before click the submit button, the value will not change, but if user click on the container again and click the TextFormField the value suddenly change.
If I add hive putAt method in TextFormField onFieldSubmitted the value will change when user click the done button, but I want the value change when user click the submit button not the done button.
GitHub Code
main.dart
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:hive_test/model/balance.dart';
import 'package:hive_test/ui/pages/main_page.dart';
Future<void> main() async {
await Hive.initFlutter();
Hive.registerAdapter(BalanceAdapter());
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainPage(),
);
}
}
balance.dart
import 'package:hive/hive.dart';
part 'balance.g.dart';
#HiveType(typeId: 1)
class Balance {
Balance({required this.value});
#HiveField(0)
int value;
}
balance.g.dart
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'balance.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class BalanceAdapter extends TypeAdapter<Balance> {
#override
final int typeId = 1;
#override
Balance read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return Balance(
value: fields[0] as int,
);
}
#override
void write(BinaryWriter writer, Balance obj) {
writer
..writeByte(1)
..writeByte(0)
..write(obj.value);
}
#override
int get hashCode => typeId.hashCode;
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is BalanceAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
main_page.dart
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:hive_test/model/balance.dart';
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
double maxWidth = MediaQuery.of(context).size.width;
Widget balance(balanceModel) {
var balanceValue = balanceModel.getAt(0);
TextEditingController balanceController =
TextEditingController(text: balanceValue.value.toString());
return ValueListenableBuilder(
valueListenable: Hive.box('balance').listenable(),
builder: (context, box, widget) {
return Align(
alignment: Alignment.topCenter,
child: Container(
margin: const EdgeInsets.only(top: 10),
child: ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
scrollable: true,
title: Container(
alignment: Alignment.center,
child: Text('Jumlah'),
),
content: Padding(
padding: const EdgeInsets.all(5.0),
child: Form(
child: TextFormField(
controller: balanceController,
decoration: InputDecoration(
labelText: 'Saldo',
icon: Icon(Icons.money_sharp),
),
keyboardType: TextInputType.number,
),
),
),
actions: [
ElevatedButton(
child: Text("Submit"),
onPressed: () {
balanceModel.putAt(
0,
Balance(
value: int.parse(balanceController.text),
),
);
Navigator.pop(context);
},
style: ElevatedButton.styleFrom(
fixedSize: Size(maxWidth * 9 / 10, 50),
),
),
],
);
},
);
},
child: Text(
'Balance: ${balanceValue.value.toString()}',
),
style: ElevatedButton.styleFrom(
fixedSize: Size(maxWidth * 9 / 10, 50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
),
),
),
);
},
);
}
return Scaffold(
appBar: AppBar(),
body: FutureBuilder(
future: Hive.openBox('balance'),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Center(
child: Text(
snapshot.error.toString(),
),
);
} else {
var balanceModel = Hive.box('balance');
if (balanceModel.length == 0) {
balanceModel.add(Balance(value: 0));
}
return balance(balanceModel);
}
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}
The Done Button
the keyboard submit is triggered with the onSubmit method property in the TextFormField, so instead of calling the Hive.puAt() in the onPressed property of ElevatedButton.
You need to make assign a GlobalKey to the TextFomrField then submit with from the ElevatedButton

What is the correct Provider<AuthBlock> for my DrawerNavigation Widget?

I am working on a simple mobile app with Google sign in capabilities.
So far, the app has been working, but with the latest step (the actual signing in) I am receiving the following error
════════ Exception caught by widgets library ═══════════════════════════════════
Error: Could not find the correct Provider<AuthBlock> above this DrawerNavigation Widget
I have tried the usual suspects; ensuring that the right packages are being imported, that it is due to me running on a virtual mobile device, and that a hot reboot might have been needed.
The issue persists however.
I had expected it to prompt the user to sign in with Google, but that is obviously not happening.
My code is as follows, and I believe the error is originating from line 54 which reads final authBlock = Provider.of(context);.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:to_do_list/blocks/auth_block.dart';
import 'package:to_do_list/screens/home_screen.dart';
import 'package:to_do_list/screens/categories_screen.dart';
import 'package:to_do_list/screens/todos_by_category.dart';
import 'package:to_do_list/service/category_service.dart';
import 'package:flutter_signin_button/flutter_signin_button.dart';
class DrawerNavigation extends StatefulWidget {
#override
_DrawerNavigationState createState() => _DrawerNavigationState();
}
class _DrawerNavigationState extends State<DrawerNavigation> {
//List<Widget> _categoryList = List<Widget>(); //Has been depricated
List<Widget> _categoryList = List<Widget>.generate(0, (index) {
//Widget obj = Widget();
//obj.id = index;
return;
});
CategoryService _categoryService = CategoryService();
#override
initState() {
super.initState();
getAllCategories();
}
getAllCategories() async {
var categories = await _categoryService.readCategories();
categories.forEach((category) {
setState(() {
_categoryList.add(InkWell(
onTap: () => Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => new TodosByCategory(
category: category['name'],
))),
child: ListTile(
title: Text(category['name']),
),
));
});
});
}
#override
Widget build(BuildContext context) {
final authBlock = Provider.of<AuthBlock>(context);
return Container(
child: Drawer(
child: ListView(
children: <Widget>[
// UserAccountsDrawerHeader(
// currentAccountPicture: CircleAvatar(
// backgroundImage: NetworkImage(
// 'https://cdn.shopify.com/s/files/1/1733/6579/products/product-image-602960373_1024x1024#2x.jpg')),
// accountName: Text('Meena Bobeena'),
// accountEmail: Text('meena#happycat.com'),
// decoration: BoxDecoration(color: Colors.blue),
// ),
Column(
children: [
SignInButton(
Buttons.Google,
onPressed: () => authBlock.loginWithGoogle(),
),
],
),
ListTile(
leading: Icon(Icons.home),
title: Text('Home'),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(builder: (context) => (HomeScreen()))),
),
ListTile(
leading: Icon(Icons.view_list),
title: Text('Categories'),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => (CategoriesScreen()))),
),
Divider(),
Column(
children: _categoryList,
)
],
),
),
);
}
}
As requested, the following is how the DrawerNavigation is called.
import 'package:flutter/material.dart';
import 'package:to_do_list/helpers/drawer_navigation.dart';
import 'package:to_do_list/screens/todo_screen.dart';
import 'package:to_do_list/service/todo_service.dart';
import 'package:to_do_list/models/todo.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
TodoService _todoService;
//List<Todo> _todoList = List<Todo>(); // Has been deprecated
List<Todo> _todoList = List<Todo>.generate(0, (index) {
Todo obj = Todo();
obj.id = index;
return obj;
});
#override
initState() {
super.initState();
getAllTodos();
}
getAllTodos() async {
_todoService = TodoService();
//_todoList = List<Todo>(); //Has been depricated
_todoList = List<Todo>.generate(0, (index) {
Todo obj = Todo();
obj.id = index;
return obj;
});
var todos = await _todoService.readTodos();
todos.forEach((todo) {
setState(() {
var model = Todo();
model.id = todo['id'];
model.title = todo['title'];
model.description = todo['description'];
model.category = todo['category'];
model.todoDate = todo['todoDate'];
model.isFinished = todo['isFinished'];
_todoList.add(model);
});
});
}
_deleteFormDialog(BuildContext context, categoryId) {
return showDialog(
context: context,
barrierDismissible: true,
builder: (param) {
return AlertDialog(
actions: <Widget>[
TextButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.red),
foregroundColor: MaterialStateProperty.all(Colors.white)),
onPressed: () async {
Navigator.pop(context);
},
child: Text('Cancel'),
),
TextButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.green),
foregroundColor: MaterialStateProperty.all(Colors.white)),
// This doesn't delete :'(
onPressed: () async {
var result = await _todoService.deleteTodos(categoryId);
//print(result);
if (result > 0) {
print(result);
Navigator.pop(context);
getAllTodos();
_showSuccessSnackBar(Text('Deleted'));
}
},
child: Text('Delete'),
),
],
title: Text('Are you sure you wish to delete this To Do item ?'),
);
});
}
_showSuccessSnackBar(message) {
var _snackBar = SnackBar(content: message);
ScaffoldMessenger.of(context).showSnackBar(_snackBar);
//_globalKey.currentState.showSnackBar(_snackBar); === Depreciated
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Meenas To Do List')),
drawer: DrawerNavigation(),
body: ListView.builder(
itemCount: _todoList.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(top: 8, left: 8, right: 8),
child: Card(
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: ListTile(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(_todoList[index].title ?? 'No Title'),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
_deleteFormDialog(context, _todoList[index].id);
},
)
],
),
subtitle: Text(_todoList[index].category ?? 'No Category'),
trailing: Text(_todoList[index].todoDate ?? 'No Date'),
),
),
);
}),
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => TodoScreen())),
child: Icon(Icons.add)),
);
}
}
This is the code where I instantiate the provider
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:to_do_list/service/auth_service.dart';
class AuthBlock {
final authService = AuthService();
final googleSignIn = GoogleSignIn(scopes: ['email']);
Stream<User> get currentUser => authService.currentUser;
loginWithGoogle() async {
try {
final GoogleSignInAccount googleUser = await googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
idToken: googleAuth.idToken,
accessToken: googleAuth.accessToken,
);
//Firebase Sign In
final result = await authService.signInWithCredential(credential);
print('${result.user.displayName}');
} catch (error) {
print(error);
}
}
logout() {
authService.logout();
}
}
The main.dart
import 'package:flutter/material.dart';
import 'package:to_do_list/src/app.dart';
void main() => runApp(App());
Did you wrap your app with the provider? E.g. like you see here
runApp(
/// Providers are above [MyApp] instead of inside it, so that tests
/// can use [MyApp] while mocking the providers
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
],
child: const MyApp(),
),
);

How to test showDialog content in flutter?

is there a way to test the content within a show dialog?
I am trying to do BDD in the project, The following is the scenario:
As a User, I would like to add a photo or select one from the gallery so that I can use it on the item.
The following is the code I am using to test it but for some reason, the test fails.
add_item_view.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:my_app_mobile/models/graded_item/graded_item.dart';
import 'package:my_app_mobile/template/index.dart' as template;
import 'package:image_picker/image_picker.dart';
class AddItemView extends HookWidget {
final GradedItem gradedItem;
static final Key photoKey = Key('#photoKey');
static final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final void Function() onPhoto;
final ImagePicker _imagePicker = ImagePicker();
AddItemView({
#required this.gradedItem,
this.onPhoto,
});
#override
Widget build(BuildContext context) {
final _image = useState<File>();
Future getImage() async {
final pickedFile = await _imagePicker.getImage(source: ImageSource.camera);
if (pickedFile != null) {
_image.value = File(pickedFile.path);
} else {
print('No image selected.');
}
}
return Scaffold(
appBar: AppBar(),
body: SingleChildScrollView(
child: Form(
key: formKey,
child: Column(
children: [
GestureDetector(
onTap: () async {
if (onPhoto != null) {
onPhoto();
}
showDialog(
context: context,
barrierColor: Colors.red.withOpacity(.2),
builder: (context) {
return CameraOptions();
},
);
},
child: Container(
key: photoKey,
alignment: Alignment.center,
child: Icon(
Icons.add_a_photo,
color: Theme.of(context).primaryColor,
size: 44.0,
),
height: 100.0,
width: 100.0,
decoration: BoxDecoration(
color: template.colors.grey340,
borderRadius: BorderRadius.circular(10.0),
),
),
),
],
),
),
),
);
}
}
class CameraOptions extends StatelessWidget {
static final Key captureButtonPhotoKey = Key('#captureButtonPhotoKey');
static final Key chooseButtonPhotoKey = Key('#chooseButtonPhotoKey');
static final Key cancelButtonKey = Key('#cancelButtonKey');
final void Function() onCapture;
final void Function() onChoose;
final void Function() onCancel;
CameraOptions({this.onCapture, this.onChoose, this.onCancel});
#override
Widget build(BuildContext context) {
return Center(
child: Container(
height: 200.0,
width: 200.0,
child: Icon(Icons.camera, color: Colors.blue),
),
);
}
}
add_item_view_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'my_app_mobile/models/graded_item/graded_item.dart';
import 'my_app_mobile/views/dashboard/children/collection/children/add_item/add_item.dart';
import 'package:mockito/mockito.dart';
void main() {
Widget mountApp({GradedItem gradedItem, void Function() onPhoto}) {
return MaterialApp(
home: AddItemView(
gradedItem: gradedItem,
onPhoto: onPhoto,
),
);
}
testWidgets('should build with no problems', (tester) async {
await tester.pumpWidget(mountApp(gradedItem: GradedItem.generate()));
expect(find.byType(AddItemView), findsOneWidget);
});
group('photo', () {
testWidgets(
'should photo widget be available',
(tester) async {
await tester.pumpWidget(mountApp(
gradedItem: GradedItem.generate(),
));
expect(find.byKey(AddItemView.photoKey), findsOneWidget);
},
);
testWidgets(
'should be able to call onPhoto handler when is available',
(tester) async {
final fn = MockedFunctions();
await tester.pumpWidget(mountApp(
gradedItem: GradedItem.generate(),
onPhoto: fn.onPhoto,
));
expect(find.byKey(AddItemView.photoKey), findsOneWidget);
await tester.tap(find.byKey(AddItemView.photoKey));
verify(fn.onPhoto()).called(1);
},
);
testWidgets(
'should onPhoto handler open camera options',
(tester) async {
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Builder(
builder: (BuildContext context) {
return AddItemView(
gradedItem: GradedItem.generate(),
// onPhoto: fn.onPhoto,
);
},
),
),
),
);
await tester.tap(find.byKey(AddItemView.photoKey));
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.byIcon(Icons.camera), findsOneWidget);
},
);
});
}
class MockedFunctions extends Mock {
void onPhoto();
}
Is there a way to do testing on a showDialog function?
Solved, for some reason, the code I posted is working, I restarted my computer and now they are working.

problem putting progressDialog.shide () in flutter

I am new to flutter and I am having the following problem.
I am trying to use the progressDialog in in a listview, I make the query to my database, I extract the list and pass it to the listview, I am trying to use the progressDialog so when I start loading the list it will run and tell the user to wait, and when I finish loading the list then the progressDialog is hidden, so far it works for me by bringing me the list and the progressDialog is executed saying to wait, but when I put the progressDialog.hide where the loading of the list ends I this is not accepting that line of code (progressDialog .hidde)
image:
enter image description here
import 'dart:convert';
import 'package:fluterproyecto1/Modulos/DetalleUser.dart';
import 'package:flutter/material.dart';
import 'package:progress_dialog/progress_dialog.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
String username2 = '';
String profesion = '';
String name = '';
class MemberPage extends StatefulWidget {
MemberPage({Key key}) : super(key: key);
#override
_MemberPageState createState() => _MemberPageState();
}
class _MemberPageState extends State<MemberPage> {
Map data;
List userData;
ProgressDialog progressDialog;
String name = '';
Future getData() async {
http.Response response =
await http.get("http://masciudad.com.co/flutter/getdata.php");
data = json.decode(response.body);
//setState(() {
//progressDialog.show();
userData = data["data"];
//progressDialog.hide();
//});
}
#override
void initState() {
super.initState();
getData();
}
#override
Widget build(BuildContext context) {
progressDialog = ProgressDialog(context, type: ProgressDialogType.Normal);
progressDialog.style(message: 'Por favor espere...');
progressDialog.show();
setState(() {
obtenerPreferencias();
});
return Scaffold(
appBar: AppBar(
title: Text("Bienvenido $username2"),
),
body: ListView.builder(
itemCount: userData == null ? 0 : userData.length,
itemBuilder: (BuildContext context, int index) {
return InkWell(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
children: <Widget>[
Image.asset(
"assets/128.jpg",
width: 30.0,
height: 30.0,
),
//CircleAvatar(
///cuando la imagen es de interntet
//backgroundImage: NetworkImage(
// "https://s3.amazonaws.com/uifaces/faces/twitter/follettkyle/128.jpg"),
//),
Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
"${userData[index]["username"]} - ${userData[index]["profesion"]}",
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w700,
),
),
)
],
),
),
onTap: () => Navigator.push(
context,
new MaterialPageRoute(
builder: (BuildContext context) =>
new DetalleUser(name: userData[index]["username"]))),
);
},
),
//Navigator.of(context).push(MaterialPageRoute(
// builder: (BuildContext context) => MyHomePage()));
//Navigator.pushReplacementNamed(context, "/MyHomePage");
);
}
Future obtenerPreferencias() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
setState(() {
username2 = preferences.get("username2") ?? "";
profesion = preferences.get("profesion") ?? "";
});
}
Future destruirPreferencias() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
preferences.clear();
}
}
You can copy paste run full code below
You can use addPostFrameCallback and put logical in another function getRelatedData()
code snippet
void getRelatedData() async {
progressDialog = ProgressDialog(context, type: ProgressDialogType.Normal);
progressDialog.style(message: 'Por favor espere...');
progressDialog.show();
await getData();
await obtenerPreferencias();
progressDialog.hide();
}
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
getRelatedData();
});
}
working demo
full code
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:progress_dialog/progress_dialog.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
String username2 = '';
String profesion = '';
String name = '';
class MemberPage extends StatefulWidget {
MemberPage({Key key}) : super(key: key);
#override
_MemberPageState createState() => _MemberPageState();
}
class _MemberPageState extends State<MemberPage> {
Map data;
List userData;
ProgressDialog progressDialog;
String name = '';
Future getData() async {
http.Response response =
await http.get("http://masciudad.com.co/flutter/getdata.php");
data = json.decode(response.body);
//setState(() {
//progressDialog.show();
userData = data["data"];
print("getData Done");
//progressDialog.hide();
//});
}
void getRelatedData() async {
progressDialog = ProgressDialog(context, type: ProgressDialogType.Normal);
progressDialog.style(message: 'Por favor espere...');
progressDialog.show();
await getData();
await obtenerPreferencias();
progressDialog.hide();
}
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
getRelatedData();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Bienvenido $username2"),
),
body: ListView.builder(
itemCount: userData == null ? 0 : userData.length,
itemBuilder: (BuildContext context, int index) {
return InkWell(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
children: <Widget>[
Image.network(
"https://picsum.photos/250?image=9",
width: 30.0,
height: 30.0,
),
//CircleAvatar(
///cuando la imagen es de interntet
//backgroundImage: NetworkImage(
// "https://s3.amazonaws.com/uifaces/faces/twitter/follettkyle/128.jpg"),
//),
Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
"${userData[index]["username"]} - ${userData[index]["profesion"]}",
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w700,
),
),
)
],
),
),
onTap: () {
/*Navigator.push(
context,
new MaterialPageRoute(
builder: (BuildContext context) =>
new DetalleUser(name: userData[index]["username"])));*/
});
},
),
//Navigator.of(context).push(MaterialPageRoute(
// builder: (BuildContext context) => MyHomePage()));
//Navigator.pushReplacementNamed(context, "/MyHomePage");
);
}
Future obtenerPreferencias() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
setState(() {
username2 = preferences.get("username2") ?? "";
profesion = preferences.get("profesion") ?? "";
});
}
Future destruirPreferencias() async {
SharedPreferences preferences = await SharedPreferences.getInstance();
preferences.clear();
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MemberPage(),
);
}
}
Dear #Yeimer I would suggest read documentation carefully
https://pub.dev/packages/progress_dialog
Add the Package this
dependencies:
progress_dialog: ^1.2.4
1
Initialize your ProgressDialog object
final ProgressDialog prDialog = ProgressDialog(context);
//For normal dialog
prDialog = ProgressDialog(context,type: ProgressDialogType.Normal, isDismissible: true/false, showLogs: true/false);
2 Showing the progress dialog
await pr.show();
3
Dismissing the progress dialog
prDialog.hide().then((isHidden) {
print(isHidden);
});
// or simply
await prDialog.hide();

Flutter testWidgets with flutter_bloc - tests fail only when executed together

I'm having a problem with the attached widget tests in flutter. When I run the tests individually, each of them succeeds; however, when I run the entire main() method, the first three tests succeed but the last two fail with the following exception:
Expected: exactly one matching node in the widget tree
Actual: ?:<zero widgets with type "SuccessDialog" (ignoring offstage widgets)>
I understand that the exception means that the widget I'm expecting is not present - what I don't understand is why the test succeeds when run individually but fails after being run after other tests. Is there some instance I need to be "resetting" after each test?
I've tried inserting "final SemanticsHandle handle = tester.ensureSemantics();" at the start of each tests and "handle.dispose();" at the end of each test but got the same results.
EDIT:
After some further investigating it seems like the problem may be with how I manage bloc instances using the flutter_bloc package. I have altered my tests to create a new testWidget instance for each test but am still encountering the same problem. Is there anything I may be missing that would cause a bloc instance to persist across testWidget objects?
My new test code looks like this:
main() {
MvnoMockClient.init();
testWidgets(
'Voucher Redemption: Tapping redeem when no values were entered yields 2 field errors',
(WidgetTester tester) async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
home: VoucherRedemptionPage(onSuccess: () {}, onFail: () {}),
),
);
await tester.pumpWidget(testWidget);
await tester.tap(find.byType(PrimaryCardButton));
await tester.pump();
expect(find.text("Field is required"), findsNWidgets(2));
});
testWidgets(
'Voucher Redemption: Tapping redeem when only voucher number was entered yields one field error',
(WidgetTester tester) async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
home: VoucherRedemptionPage(onSuccess: () {}, onFail: () {}),
),
);
await tester.pumpWidget(testWidget);
await tester.enterText(find.byType(PlainTextField), "0000000000");
await tester.tap(find.byType(PrimaryCardButton));
await tester.pump();
expect(find.text("Field is required"), findsOneWidget);
});
testWidgets(
'Voucher Redemption: Tapping redeem when only mobile number was entered yields one field error',
(WidgetTester tester) async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
home: VoucherRedemptionPage(onSuccess: () {}, onFail: () {}),
),
);
await tester.pumpWidget(testWidget);
await tester.enterText(find.byType(MsisdnField), "0815029249");
await tester.tap(find.byType(PrimaryCardButton));
await tester.pump();
expect(find.text("Field is required"), findsOneWidget);
});
testWidgets(
'Voucher Redemption: A successful server response yields a success dialog',
(WidgetTester tester) async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
home: VoucherRedemptionPage(onSuccess: () {}, onFail: () {}),
),
);
await tester.pumpWidget(testWidget);
await tester.enterText(find.byType(PlainTextField), "0000000000");
await tester.enterText(find.byType(MsisdnField), "0815029249");
await tester.tap(find.text("REDEEM"));
await tester.pump();
expect(find.byType(SuccessDialog), findsOneWidget);
});
testWidgets(
'Voucher Redemption: An unsuccessful server response yields an error dialog',
(WidgetTester tester) async {
Widget testWidget = MediaQuery(
data: MediaQueryData(),
child: MaterialApp(
home: VoucherRedemptionPage(onSuccess: () {}, onFail: () {}),
),
);
await tester.pumpWidget(testWidget);
gToken = "invalid";
await tester.enterText(find.byType(PlainTextField), "0000000000");
await tester.enterText(find.byType(MsisdnField), "0815029249");
await tester.tap(find.byType(PrimaryCardButton));
await tester.pump();
gToken = "validToken";
expect(find.byType(ErrorDialog), findsOneWidget);
});
}
For additional reference, I have also included the code for the VoucherRedemptionPage and VoucherRedemptionScreen below:
class VoucherRedemptionPage extends StatelessWidget {
final onSuccess;
final onFail;
const VoucherRedemptionPage({Key key, #required this.onSuccess, #required this.onFail})
: super(key: key);
#override
Widget build(BuildContext context) {
var _voucherRedemptionBloc = new VoucherRedemptionBloc();
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/" + gFlavor + "/primary_background.png"),
fit: BoxFit.cover),
),
child: new Scaffold(
backgroundColor: Colors.transparent,
appBar: new AppBar(
title: new Text(gDictionary.find("Redeem Voucher")),
),
body: new VoucherRedemptionScreen(
voucherRedemptionBloc: _voucherRedemptionBloc,
onSuccess: this.onSuccess,
onFail: this.onFail,
),
),
);
}
}
class VoucherRedemptionScreen extends StatefulWidget {
const VoucherRedemptionScreen({
Key key,
#required VoucherRedemptionBloc voucherRedemptionBloc,
#required this.onSuccess,
#required this.onFail,
}) : _voucherRedemptionBloc = voucherRedemptionBloc,
super(key: key);
final VoucherRedemptionBloc _voucherRedemptionBloc;
final onSuccess;
final onFail;
#override
VoucherRedemptionScreenState createState() {
return new VoucherRedemptionScreenState(
_voucherRedemptionBloc, onSuccess, onFail);
}
}
class VoucherRedemptionScreenState extends State<VoucherRedemptionScreen> {
final VoucherRedemptionBloc _voucherRedemptionBloc;
final onSuccess;
final onFail;
TextEditingController _msisdnController = TextEditingController();
TextEditingController _voucherPinController = TextEditingController();
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
VoucherRedemptionScreenState(
this._voucherRedemptionBloc, this.onSuccess, this.onFail);
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<VoucherRedemptionEvent, VoucherRedemptionState>(
bloc: _voucherRedemptionBloc,
builder: (
BuildContext context,
VoucherRedemptionState currentState,
) {
if (currentState is VoucherRedemptionInitial) {
_voucherPinController.text = currentState.scannedNumber;
return _buildFormCard();
}
if (currentState is VoucherRedemptionLoading) {
return Center(
child: CircularProgressIndicator(),
);
}
if (currentState is VoucherRedemptionSuccess) {
return SuccessDialog(
title: gDictionary.find("Voucher Redeemed Successfully"),
description: currentState.successMessage,
closeText: gDictionary.find("OK"),
closeAction: () {
this.onSuccess();
_voucherRedemptionBloc.dispatch(ResetVoucherRedemptionState());
},
);
}
if (currentState is VoucherRedemptionError) {
return ErrorDialog(
errorCode: currentState.errorCode,
errorMessage: currentState.errorMessage,
closeText: gDictionary.find("OK"),
closeAction: () {
this.onFail();
_voucherRedemptionBloc.dispatch(ResetVoucherRedemptionState());
},
);
}
},
);
}
Widget _buildFormCard() {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8), topRight: Radius.circular(8))),
padding: EdgeInsets.fromLTRB(12, 12, 12, 0),
width: double.infinity,
height: double.infinity,
child: _buildCardContent(),
);
}
Widget _buildCardContent() {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
gDictionary.find("Transaction Amount"),
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryColorDark,
fontWeight: FontWeight.bold),
),
Container(height: 16),
Form(
key: _formKey,
child: _buildFormContent(),
),
],
),
);
}
Column _buildFormContent() {
return Column(
children: <Widget>[
PlainTextField(
controller: _voucherPinController,
label: gDictionary.find("Voucher Number"),
required: true,
),
Container(height: 16),
MsisdnField(
controller: _msisdnController,
label: gDictionary.find("Mobile Number"),
required: true,
),
Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
SecondaryCardButton(
text: gDictionary.find("SCAN VOUCHER"),
onPressed: () {
_voucherRedemptionBloc.dispatch(
ScanBarcode(),
);
},
),
Container(
width: 8.0,
),
PrimaryCardButton(
text: gDictionary.find("REDEEM"),
onPressed: () {
if (_formKey.currentState.validate()) {
_voucherRedemptionBloc.dispatch(
RedeemVoucher(
_voucherPinController.text,
_msisdnController.text,
),
);
}
},
),
],
)
],
);
}
}
Found the problem. I was using the singleton pattern when creating an instance of the bloc - this caused states to persist across different widget objects. Very unlikely that anyone will encounter the same problem that I did but below is the code that I changed to mitigate the problem
Old problematic code:
class VoucherRedemptionBloc
extends Bloc<VoucherRedemptionEvent, VoucherRedemptionState> {
static final VoucherRedemptionBloc _voucherRedemptionBlocSingleton =
new VoucherRedemptionBloc._internal();
factory VoucherRedemptionBloc() {
return _voucherRedemptionBlocSingleton;
}
VoucherRedemptionBloc._internal();
//...
}
Updated working code:
class VoucherRedemptionBloc
extends Bloc<VoucherRedemptionEvent, VoucherRedemptionState> {
VoucherRedemptionBloc();
//...
}
That likely happens because your tests mutate some global variable but do not reset their value.
One way to make it safe is to always use setUp and tearDown instead of mutating variables directly the main scope:
int global = 0;
void main() {
final initialGlobalValue = global;
setUp(() {
global = 42;
});
tearDown(() {
global = initialGlobalValue;
});
test('do something with "global"', () {
expect(++global, 43);
});
test('do something with "global"', () {
// would fail without setUp/tearDown
expect(++global, 43);
});
}
Similarly, if a test needs to modify a variable, use addTearDown instead of manually resetting the value later in the test.
DON'T:
int global = 0;
test("don't", () {
global = 43;
expect(global, 43);
global = 0;
})
DO:
int global = 0;
test('do', () {
global = 43;
addTearDown(() => global = 0);
expect(global, 43);
});
This ensures that the value will always be reset even if the tests fails – so that other test functions normally.
In my case I wasn't setting skipOffstage: false, doing something like this worked for me:
expect(find.text('text', skipOffstage: false), findsNWidgets(2));