Flutter - How to select DropdownButton item in widget test - flutter

I tried selecting a DropdownButton item like this:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:dropdown_test_sample/main.dart';
void main() {
testWidgets('Select Dropdown item test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const SampleApp());
final dropdown = find.byKey(const ValueKey('dropdown'));
await tester.tap(dropdown);
await tester.pumpAndSettle();
final dropdownItem = find.byKey(const ValueKey('First item key'));
await tester.tap(dropdownItem);
await tester.pumpAndSettle();
});
}
But unfortunately, It throws this exception:
There seems to be something that keeps creating an identical DropdownButton item with the same key, thereby making the widget test fail because, tester.tap() cannot "tap" on two widgets at the same time.
Here's the full implementation of the DropdownButton widget:
import 'package:flutter/material.dart';
void main() {
runApp(const SampleApp());
}
class SampleApp extends StatelessWidget {
const SampleApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Dropdown Test Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Home(),
);
}
}
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: DummyDropdown(
key: ValueKey('dropdown'),
),
),
);
}
}
class DummyDropdown extends StatefulWidget {
const DummyDropdown({Key? key}) : super(key: key);
#override
State<DummyDropdown> createState() => _DummyDropdownState();
}
class _DummyDropdownState extends State<DummyDropdown> {
String? text = 'Dropdown';
String? textValue;
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: textValue,
underline: Container(),
dropdownColor: Theme.of(context).cardColor,
style: Theme.of(context).textTheme.bodyText2,
hint: Text(
text!,
),
icon: const Icon(Icons.keyboard_arrow_down),
onChanged: (newValue) {
setState(
() {
textValue = newValue;
text = newValue;
},
);
},
items: <String>['First item', 'Second item', 'Third item']
.map<DropdownMenuItem<String>>(
(value) {
return DropdownMenuItem<String>(
value: value,
key: ValueKey('$value key'),
child: Text(
value,
),
);
},
).toList(),
);
}
}

Widget testing in drop down button is different than other widgets please see here for more. In this is case select the last element from the text "First Item".This will work.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:testapp/main.dart';
void main() {
testWidgets('Select Dropdown item test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const SampleApp());
final dropdown = find.byKey(const ValueKey('dropdown'));
await tester.tap(dropdown);
await tester.pumpAndSettle();
///if you want to tap first item
final dropdownItem = find.text('First item').last;
await tester.tap(dropdownItem);
await tester.pumpAndSettle();
});
}
The issue is here. when we tap the drop down one was already selected and other one is not selected. So you can test that item using the text value.
final dropdownItem = find.text('First item').last;

Related

How to show updated list in shared preferences on UI - Flutter

I am making an app in a flutter in which I can select the contacts from phone book and saving them in shared preferences. No problem in data saving and retrieving but i m struggling with showing the updated list on my UI. It is showing the contacts list but every time I click on Load button it duplicates the list and showing 2 lists , 1 previous and other updated .
how can i show just updated list on UI ?
here is my code:
import 'package:contacts_test/select_contacts.dart';
import 'package:contacts_test/shared_pref.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import 'contact_model.dart';
void main() {
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 const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
SharedPref sharedPref = SharedPref();
ContactModel modelLoad = ContactModel(displayName: 'saniya' , phoneNumber: '324235 ');
List _list = [];
#override
initState() {
super.initState();
// Add listeners to this clas
// loadSharedPrefs();
}
loadSharedPrefs() async {
try {
print('in load shared pref-- getting keys ');
final prefs = await SharedPreferences.getInstance();
final keys = prefs.getKeys();
print('now load shared pref ');
for (String key in keys) {
ContactModel user = ContactModel.fromJson(await sharedPref.read(key));
_list.add(user);
}
print('done load shared pref ');
}
catch (Excepetion) {
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("contacts "),
),
body: Builder(
builder: (context) {
return Column(children: [
RaisedButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Plugin1()));
},
child: const Text('fluttercontactpicker - plugin1'),
),
RaisedButton(
onPressed: () async {
await loadSharedPrefs();
},
child: Text('Load', style: TextStyle(fontSize: 20)),
),
Expanded(
child: _list.isNotEmpty ?
ListView.builder(
shrinkWrap: true,
itemCount: _list.length,
itemBuilder: (context, position) {
return ListTile(
leading: Icon(Icons.contacts),
title: Text(
_list[position].displayName.toString()
),
trailing: Icon(Icons.delete));
},
) : Center(child: Text('No list items to show')),
),
]);
}
),
);
}
}
Your loadSharedPrefs(); function adds each contact to the list you show. Every time you press the button, the same elements are added again to the list. There are multiple ways to avoid that. You can: empty the list before filling it, you can write a for loop to loop over the length of the incoming contacts and for each to add it to the list by always starting from index 0. In case you use some kind of replacement or removing method, make sure you call setState(()=> { });
Base on the answer, here is a possible solution:
import 'package:contacts_test/select_contacts.dart';
import 'package:contacts_test/shared_pref.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import 'contact_model.dart';
void main() {
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 const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
SharedPref sharedPref = SharedPref();
ContactModel modelLoad = ContactModel(displayName: 'saniya' , phoneNumber: '324235 ');
List _list = [];
#override
initState() {
super.initState();
// Add listeners to this clas
// loadSharedPrefs();
}
loadSharedPrefs() async {
try {
print('in load shared pref-- getting keys ');
final prefs = await SharedPreferences.getInstance();
final keys = prefs.getKeys();
print('now load shared pref ');
var newList = [];
for (String key in keys) {
ContactModel user = ContactModel.fromJson(await sharedPref.read(key));
newList.add(user);
}
setState(()=> { _list = newList; });
print('done load shared pref ');
}
catch (Excepetion) {
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("contacts "),
),
body: Builder(
builder: (context) {
return Column(children: [
RaisedButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Plugin1()));
},
child: const Text('fluttercontactpicker - plugin1'),
),
RaisedButton(
onPressed: () async {
await loadSharedPrefs();
},
child: Text('Load', style: TextStyle(fontSize: 20)),
),
Expanded(
child: _list.isNotEmpty ?
ListView.builder(
shrinkWrap: true,
itemCount: _list.length,
itemBuilder: (context, position) {
return ListTile(
leading: Icon(Icons.contacts),
title: Text(
_list[position].displayName.toString()
),
trailing: Icon(Icons.delete));
},
) : Center(child: Text('No list items to show')),
),
]);
}
),
);
}
}

Shared Preferences key value not changing

I want to write code that directs the user to a welcome page if it's the first time the app is being run. After the user logs in, any subsequent launches of the app direct the user to log in, skipping the welcome page. It seems that when I try to set the value of my key upon logging in, it's not changing, because after logging in, closing the app and launching it again it's still going to the welcome page. How do I fix this? I'm not sure whether the issue is with the initialisation or the setting of the value upon login.
Here's the initialisation of my app:
import 'package:screens/welcomepages/signup_or_login.dart';
import 'package:screens/welcomepages/welcome.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:config/theme.dart';
import 'package:shared_preferences/shared_preferences.dart';
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarBrightness: Brightness.dark));
runApp(MyApp(prefs: await _prefs));
}
class MyApp extends StatefulWidget {
const MyApp({Key? key, this.title, required this.prefs})
: super(key: key);
final String? title;
final SharedPreferences prefs;
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late SharedPreferences prefs;
#override
void initState() {
super.initState();
initializePreference();
}
Future<void> initializePreference() async {
prefs = await SharedPreferences.getInstance();
setState(() {
prefs.setBool("hasLoggedIn", false);
});
}
#override
Widget build(BuildContext context) {
if (prefs.getBool("hasLoggedIn") == false) {
return MaterialApp(
theme: theme(),
debugShowCheckedModeBanner: false,
home: Welcome(prefs: prefs),
);
}
return MaterialApp(
theme: theme(),
debugShowCheckedModeBanner: false,
home: SignUpOrLogIn(prefs: prefs),
);
}
}
And here's the relevant parts of my log in page
import 'package:services/auth/auth_service.dart';
import 'package:widgets/text_fields_widgets/email_textfield.dart';
import 'package:widgets/text_fields_widgets/password_textfeild.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Login extends StatefulWidget {
const Login({Key? key, required this.prefs}) : super(key: key);
final SharedPreferences prefs;
#override
_LoginState createState() => _LoginState(prefs);
}
class _LoginState extends State<Login> {
late final SharedPreferences prefs;
_LoginState(SharedPreferences prefs);
Future<void> initializePreference() async {
prefs = await SharedPreferences.getInstance();
}
#override
void initState() {
super.initState();
initializePreference().whenComplete(() {
setState(() {
prefs.getBool("isFirstRun");
});
});
}
#override
Widget build(BuildContext context) {
Material btnLogin = loginBTN(context);
return Scaffold(
.....
Padding(
padding: const EdgeInsets.all(8.0),
child: btnLogin,
)
}
Material loginBTN(BuildContext context) {
// ignore: unused_local_variable
final btnLogin = Material(
elevation: 5,
borderRadius: BorderRadius.circular(30),
color: Theme.of(context).primaryColor,
child: MaterialButton(
padding: const EdgeInsets.fromLTRB(20, 15, 20, 15),
minWidth: MediaQuery.of(context).size.width,
onPressed: () async {
setState(() {
prefs.setBool("hasLoggedIn", true);
});
setState(
() => loadingAnimation = true,
);
await Future.delayed(const Duration(seconds: 1));
setState(
() => loadingAnimation = false,
);
await Authservice().logInMethod(
emailController.text, passwordController.text, context, _formKey);
},
child: loadingAnimation
? const CircularProgressIndicator(
color: Colors.white,
)
: const Text(
"Log in",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 24, color: Colors.white),
),
),
);
return btnLogin;
}
}
Sorry for how lengthy this is, tried to shorten it to only provide what's relevant.
Your are calling initializePreference() inside initState() and that function set hasLoggedIn to false. So even if you set it to true in your login page, when restarting the app it will be set again to false.
Try this brother
if (prefs.getBool("hasLoggedIn") == null || prefs.getBool("hasLoggedIn") == false) {
return MaterialApp(
theme: theme(),
debugShowCheckedModeBanner: false,
home: Welcome(prefs: prefs),
);
}

How can I make the initial value of a TextFormField equal to a variable loaded from shared preferences? (Flutter)

I want to make the initial value of the TextFormField equal to the counter variable. The counter is maintained between app restarts, but when I restart the app, the initial value of the text field is always 0.
Is there a better way of doing that?
(I'm new to programming, sorry if it's a dumb question)
Here's the code I used.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Shared preferences demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Shared preferences demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
#override
void initState() {
super.initState();
_loadCounter();
}
//Loading counter value on start
_loadCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0);
});
}
//Incrementing counter after click
_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0) + 1;
prefs.setInt('counter', _counter);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
TextFormField(
initialValue: '$_counter',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Try adding a controller for TextFormField and update the value after getting it from SharedPreferences.
Something like this.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Shared preferences demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Shared preferences demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
int _counter = 0;
final myController = TextEditingController();
#override
void dispose() {
myController.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
_loadCounter();
}
_loadCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0);
myController.text = _counter.toString();
});
}
_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_counter = (prefs.getInt('counter') ?? 0) + 1;
prefs.setInt('counter', _counter);
myController.text = _counter.toString();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
TextFormField(
controller: myController,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Hope this solves your issue.

capture data from DropDownButton in flutter and send it to the database

I'm new to flutter app development and would like to ask for your help
I already learned to capture the data of a TextFormField but with the DropDownButtons I have no idea how it is I try to use controller to put a name but I get an error
I have a DropDownButton and it works for me, it is loading the list successfully and I would like to know, how to capture the data I select in the DropDownButton and send it to my database
This is the code that I am using:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _mySelection;
final String url = "http://webmyls.com/php/getdata.php";
List data = List();
Future<String> getSWData() async {
var res = await http
.get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
var resBody = json.decode(res.body);
setState(() {
data = resBody;
});
print(data);
return "Sucess";
}
#override
void initState() {
super.initState();
this.getSWData();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new DropdownButton(
//data != null,
isDense: true,
hint: new Text("Select"),
items: data.map((item) {
return new DropdownMenuItem(
child: new Text(item['item_name']),
value: item['id'].toString(),
);
}).toList(),
onChanged: (newVal) {
setState(() {
_mySelection = newVal;
});
},
value: _mySelection,
),
),
);
}
}
I hope you can help me.
the latest dropdown value is always stores in your _mySelection. So the only thing you should do is to save _mySelection in your database.

Flutter Driver scrolling through dropdown list

I would like to scroll through a drop-down list as part of a flutter driver test, however I can't seem to figure out exactly how I would do this?
I've tried using a ValueKey, and have tried digging through the Flutter Inspector in Intellij as well, but have had no luck thus far.
I have tried find.byType(Scrollable); and it doesn't seem to work even after widgetTester.tap(). Turns out I need to wait for the screen to update with widgetTester.pumpAndSettle()
testWidgets("Test DropdownButton", (WidgetTester widgetTester) async {
await widgetTester.pumpWidget(MyApp())
final dropdownButtonFinder = find.byKey(const ValueKey('DropdownButton')); final dropdownItemFinder = find.widgetWithText(InkWell, 'Item 50'); // find.textContaining() doesn't seem to work
// Tap on the DropdownButton
await widgetTester.tap(dropdownButtonFinder);
await widgetTester.pumpAndSettle(const Duration(seconds: 2));
final dropdownListFinder = find.byType(Scrollable);
expect(dropdownListFinder, findsOneWidget); // Finds Scrollable from tapping DropDownButton
// Scroll until the item to be found appears.
await widgetTester.scrollUntilVisible(dropdownItemFinder, 500.0,
scrollable: dropdownListFinder);
await widgetTester.tap(dropdownItemFinder);
await widgetTester.pumpAndSettle(const Duration(seconds: 2));
// Verify that the item contains the correct text.
expect(find.textContaining('Item 50'), findsOneWidget);
});
Main code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> mockList() => List<String>.generate(100, (i) => 'Item $i');
String? dropdownValue;
#override
Widget build(BuildContext context) {
// debugPrint('${foo!}');
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
key: Key('Widget1'),
),
body: Center(
child: DropdownButton(
key: Key('DropdownButton'),
value: dropdownValue,
onChanged: (String? newValue) {
setState(() {
dropdownValue = newValue!;
});
},
items: mockList()
.map<DropdownMenuItem<String>>(
(String value) => DropdownMenuItem<String>(
value: value,
child: Text(value),
key: Key('item$value'),
),
)
.toList(),
)
);
}
}
Running the test