Related
first of all, i do apologise if i am not describing the problem as thorough or as easy to understand as possible, forgive me as i just started learning how to build flutter chat app. i have the code below and i have integrated with firebase. i also enabled phone sign-in method and have input phone number for testing. what i am facing is that my app is able to bypass the registration screen and verification screen even though i have provided the wrong phone number and verification code. i have attached my code below and do appreciate all the help i can get.
// ignore_for_file: deprecated_member_use
import 'package:country_pickers/country.dart';
import 'package:country_pickers/country_pickers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:influential_brands_club/colors.dart';
import 'package:influential_brands_club/presentation/bloc/auth/auth_cubit.dart';
import 'package:influential_brands_club/presentation/bloc/phone_auth/phone_auth_cubit.dart';
import 'package:influential_brands_club/presentation/pages/phone_verification_page.dart';
import 'package:influential_brands_club/presentation/pages/set_initial_profile_page.dart';
import 'package:influential_brands_club/presentation/screens/home_screen.dart';
import 'package:influential_brands_club/presentation/widgets/theme/style.dart';
class RegistrationScreen extends StatefulWidget {
const RegistrationScreen({Key? key}) : super(key: key);
#override
State<RegistrationScreen> createState() => _RegistrationScreenState();
}
class _RegistrationScreenState extends State<RegistrationScreen> {
static Country _selectedFilteredDialogCountry =
CountryPickerUtils.getCountryByPhoneCode("65");
String _countryCode = _selectedFilteredDialogCountry.phoneCode;
String _phoneNumber = "";
final TextEditingController _phoneAuthController = TextEditingController();
#override
void dispose() {
_phoneAuthController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocConsumer<PhoneAuthCubit, PhoneAuthState>(
listener: (context, phoneAuthState) {
if (phoneAuthState is PhoneAuthSuccess) {
BlocProvider.of<AuthCubit>(context).loggedIn();
}
if (phoneAuthState is PhoneAuthFailure) {
Scaffold.of(context).showSnackBar(SnackBar(
backgroundColor: Colors.red,
content: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text("Invalid Code"),
Icon(Icons.error_outline)
],
),
),
));
}
}, builder: (context, phoneAuthState) {
if (phoneAuthState is PhoneAuthSmsCodeReceived) {
return PhoneVerificationPage(
phoneNumber: _phoneNumber,
);
}
if (phoneAuthState is PhoneAuthProfileInfo) {
return SetInitialProfileWidget(phoneNumber: _phoneNumber);
}
if (phoneAuthState is PhoneAuthLoading) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
if (phoneAuthState is PhoneAuthSuccess) {
return BlocBuilder<AuthCubit, AuthState>(
builder: (context, authState) {
if (authState is Authenticated) {
return HomeScreen(
uid: authState.uid,
);
}
return Container();
});
}
return _bodyWidget();
}),
);
}
Widget _bodyWidget() {
return Scaffold(
backgroundColor: Colors.black,
body: Container(
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 60),
child: Column(children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const <Widget>[
Text(""),
Text("Verify your phone number",
style: TextStyle(
fontSize: 18,
color: yellowColor,
fontWeight: FontWeight.w500)),
Icon(
Icons.more_vert,
color: Colors.white,
)
],
),
const SizedBox(
height: 30,
),
const Text(
"IB Club will send an SMS message (carrier charges may apply) to verify your phone number. Enter your country code and phone number below:",
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
),
const SizedBox(
height: 30,
),
ListTile(
tileColor: primaryColor,
onTap: _openFilteredCountryPickerDialog,
title: _buildDialogItem(_selectedFilteredDialogCountry),
),
const SizedBox(
height: 8,
),
Row(
children: <Widget>[
Container(
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1.5,
color: yellowColor,
))),
width: 80,
height: 42,
alignment: Alignment.center,
child: Text(_selectedFilteredDialogCountry.phoneCode,
style: const TextStyle(color: Colors.white)),
),
const SizedBox(
width: 12,
),
Expanded(
child: SizedBox(
height: 40,
child: TextField(
controller: _phoneAuthController,
decoration: const InputDecoration(
hintText: "Phone Number",
hintStyle: TextStyle(color: Colors.white54),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: darkPrimaryColor),
)),
cursorColor: Colors.white,
style: const TextStyle(color: Colors.white)),
),
),
],
),
const SizedBox(
width: 8,
),
Expanded(
child: Align(
alignment: Alignment.bottomCenter,
child: MaterialButton(
color: yellowColor,
onPressed: _submitVerifyPhoneNumber,
child: const Text(
"Next",
style: TextStyle(
fontSize: 18,
color: Colors.black,
),
),
),
),
)
])));
}
void _openFilteredCountryPickerDialog() {
showDialog(
context: context,
builder: (_) => Theme(
data: Theme.of(context).copyWith(
primaryColor: primaryColor,
),
child: CountryPickerDialog(
titlePadding: const EdgeInsets.all(8.0),
searchCursorColor: Colors.black,
searchInputDecoration: const InputDecoration(
hintText: "Search",
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: darkPrimaryColor),
)),
isSearchable: true,
title: const Text("Select your phone code",
style: TextStyle(color: yellowColor)),
onValuePicked: (Country country) {
setState(() {
_selectedFilteredDialogCountry = country;
_countryCode = country.phoneCode;
});
},
itemBuilder: _buildDialogItem,
),
));
}
Widget _buildDialogItem(Country country) {
return Container(
height: 40,
alignment: Alignment.center,
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: primaryBlack, width: 1),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
CountryPickerUtils.getDefaultFlagImage(country),
const SizedBox(
height: 8.0,
),
Text(" +${country.phoneCode} "),
const SizedBox(
height: 8.0,
),
SizedBox(width: 155, child: Text(country.name)),
const Spacer(),
const Icon(Icons.arrow_drop_down)
],
),
);
}
void _submitVerifyPhoneNumber() {
if (_phoneAuthController.text.isNotEmpty) {
_phoneNumber = "+$_countryCode${_phoneAuthController.text}";
BlocProvider.of<PhoneAuthCubit>(context).submitVerifyPhoneNumber(
phoneNumber: _phoneNumber,
);
}
}
}
I have an app screen that uses Checkboxes and a Drop down Menu. However, I have realised I'd coded it inside a StatelessWidget so I am unable to change the state when an option is selected. How do I get this working so that when a Checkbox is selected then it will display as being "checked" rather than empty?
I have tried pasting in my code into a Stateful Widget, however keep running into errors, as I am not totally sure on which parts should go where.
import 'package:carve_brace_app/model/activity.dart';
import 'package:flutter/material.dart';
class DetailPage extends StatelessWidget {
final Activity activity;
DetailPage({Key key, this.activity}) : super(key: key);
#override
Widget build(BuildContext context) {
final levelIndicator = Container(
child: Container(
child: LinearProgressIndicator(
backgroundColor: Color.fromRGBO(209, 224, 224, 0.2),
value: 2.0,
valueColor: AlwaysStoppedAnimation(Colors.green)),
),
);
final topContentText = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 120.0),
Container(
width: 90.0,
child: new Divider(color: Colors.green),
),
SizedBox(height: 10.0),
Text(
activity.activityName,
style: TextStyle(color: Colors.white, fontSize: 45.0),
),
SizedBox(height: 30.0),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 6,
child: Padding(
padding: EdgeInsets.only(left: 10.0),
child: Text(
"Last Run: 3-2-19\n"+
"Last Avg Strain: 34%\n"+
"Last Run Time: 00:45:23",
style: TextStyle(color: Colors.white),
))),
// Expanded(flex: 1, child: newRow)
],
),
],
);
final topContent = Stack(
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height * 0.45,
padding: EdgeInsets.all(40.0),
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
Color.fromRGBO(33, 147, 176, 100),
Color.fromRGBO(109, 213, 237, 100)
],
),
),
child: Center(
child: topContentText,
),
),
Positioned(
left: 235.0,
top: 180.0,
child: InkWell(
onTap: () {
Navigator.pop(context);
},
child: new CircleAvatar(
radius: 80.0,
backgroundImage: NetworkImage(activity.icon),
backgroundColor: Colors.white,
),
),
),
Positioned(
left: 8.0,
top: 60.0,
child: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back, color: Colors.white),
),
)
],
);
final bottomContentText = Text(
"Config:",
style: TextStyle(fontSize: 18.0),
);
final mappedCheckbox = CheckboxListTile(
title: Text("Mapped"),
value: false,
onChanged: (newValue) { },
controlAffinity: ListTileControlAffinity.leading, // <-- leading Checkbox
);
final rtCheckBox = CheckboxListTile(
title: Text("Real-time Tracking"),
value: false,
onChanged: (newValue) { },
controlAffinity: ListTileControlAffinity.leading, // <-- leading Checkbox
);
final descriptionText = Text(
"Description:",
style: TextStyle(fontSize: 12.0),
);
final description = TextFormField(
decoration: InputDecoration(
hintText: 'Enter an activity description',
),
);
final scheduledFor = Text(
"Scheduled for:",
style: TextStyle(fontSize: 12.0),
);
final dropdown = new DropdownButton<String>(
items: <String>['Now (Default)', 'B', 'C', 'D'].map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
hint: Text("Now (Default)"),
onChanged: (_) {},
);
final readButton = Container(
padding: EdgeInsets.symmetric(vertical: 16.0),
width: 170,//MediaQuery.of(context).size.width,
child: RaisedButton(
onPressed: () => {},
color: Colors.lightBlue,
child:
Text("Start", style: TextStyle(color: Colors.white, fontSize: 20)),
));
final bottomContent = Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(40.0),
child: Center(
child: Column(
children: <Widget>[bottomContentText, mappedCheckbox, rtCheckBox, descriptionText, description, Text("\n"), scheduledFor, dropdown, readButton],
),
),
);
return Scaffold(
body: Column(
children: <Widget>[topContent, bottomContent],
),
);
}
}
Hover on to the StatelessWidget class and use
Android Studio:
Mac: option + enter
Windows: alt + enter
Visual Studio Code:
Mac: cmd + .
Windows: ctrl + .
Output (Your solution):
Here is the working code.
class DetailPage extends StatefulWidget {
final Activity activity;
DetailPage({Key key, this.activity}) : super(key: key);
#override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
bool _tracking = false, _mapped = false; // you need this
String _schedule;
#override
Widget build(BuildContext context) {
final levelIndicator = Container(
child: Container(
child: LinearProgressIndicator(backgroundColor: Color.fromRGBO(209, 224, 224, 0.2), value: 2.0, valueColor: AlwaysStoppedAnimation(Colors.green)),
),
);
final topContentText = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 120.0),
Container(
width: 90.0,
child: Divider(color: Colors.green),
),
SizedBox(height: 10.0),
Text(
widget.activity.activityName,
style: TextStyle(color: Colors.white, fontSize: 45.0),
),
SizedBox(height: 30.0),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
flex: 6,
child: Padding(
padding: EdgeInsets.only(left: 10.0),
child: Text(
"Last Run: 3-2-19\n" + "Last Avg Strain: 34%\n" + "Last Run Time: 00:45:23",
style: TextStyle(color: Colors.white),
))),
// Expanded(flex: 1, child: newRow)
],
),
],
);
final topContent = Stack(
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height * 0.45,
padding: EdgeInsets.all(40.0),
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [Color.fromRGBO(33, 147, 176, 100), Color.fromRGBO(109, 213, 237, 100)],
),
),
child: Center(
child: topContentText,
),
),
Positioned(
left: 235.0,
top: 180.0,
child: InkWell(
onTap: () {
Navigator.pop(context);
},
child: CircleAvatar(
radius: 80.0,
backgroundColor: Colors.white,
),
),
),
Positioned(
left: 8.0,
top: 60.0,
child: InkWell(
onTap: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back, color: Colors.white),
),
)
],
);
final bottomContentText = Text(
"Config:",
style: TextStyle(fontSize: 18.0),
);
final mappedCheckbox = CheckboxListTile(
title: Text("Mapped"),
value: _mapped,
onChanged: (newValue) => setState(() => _mapped = newValue),
controlAffinity: ListTileControlAffinity.leading, // <-- leading Checkbox
);
final rtCheckBox = CheckboxListTile(
title: Text("Real-time Tracking"),
value: _tracking,
onChanged: (newValue) => setState(() => _tracking = newValue),
controlAffinity: ListTileControlAffinity.leading, // <-- leading Checkbox
);
final descriptionText = Text(
"Description:",
style: TextStyle(fontSize: 12.0),
);
final description = TextFormField(
decoration: InputDecoration(
hintText: 'Enter an activity description',
),
);
final scheduledFor = Text(
"Scheduled for:",
style: TextStyle(fontSize: 12.0),
);
final dropdown = DropdownButton<String>(
value: _schedule,
items: <String>['Now (Default)', 'B', 'C', 'D'].map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
hint: Text("Now (Default)"),
onChanged: (newValue) {
setState(() {
_schedule = newValue;
});
},
);
final readButton = Container(
padding: EdgeInsets.symmetric(vertical: 16.0),
width: 170, //MediaQuery.of(context).size.width,
child: RaisedButton(
onPressed: () => {},
color: Colors.lightBlue,
child: Text("Start", style: TextStyle(color: Colors.white, fontSize: 20)),
));
final bottomContent = Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(40.0),
child: Center(
child: Column(
children: <Widget>[bottomContentText, mappedCheckbox, rtCheckBox, descriptionText, description, Text("\n"), scheduledFor, dropdown, readButton],
),
),
);
return Scaffold(
body: Column(
children: <Widget>[topContent, bottomContent],
),
);
}
}
For VSCode (Visual Studio Code) use ctrl + '.' keys while the cursor on the stateless widget to convert it to stateful widget.
You could use intellij's or vscode shortcut by hitting alt + enter or selecting the bulb icon while your cursor is on the name of the stateless widget then select convert to stateful widget
For Android Studio in Mac:
1.Place the marker on the Stateless widget name
2.Hit Alt+Enter
3.Select Convert to Stateful widget
4.Configure your Stateful widget based on your requirements
Adding a solution for Android Studio since I didn't find one here.
Place the marker on the Stateless widget name:
Hit Alt+Enter
Select Convert to Stateful widget
Configure your Stateful widget based on your requirements
I'm new on Flutter and I'm now working on my first project, then in one of my page I'm building form in order to collect informations, so I created two Card but went I'm trying adding some TextField in the second one, all the the content disappear.
Please Help :(
import 'package:flutter/material.dart';
import 'customInputField.dart';
class DropDown extends StatefulWidget {
#override
_DropDownState createState() => _DropDownState();
}
class _DropDownState extends State<DropDown> {
// Here we're going to define the LIST
List _cities = ["Douala","Yaoundé"];
List _weight = ["0-1","1-3","3-5"];
List _dimensions = ["A5","A4","A3","A2"];
// End
var resultsList = new List.filled(3, "");
#override
void initState(){
// Here we're going to set the initial values
resultsList[0] = "Douala";
resultsList[1] = "0-1";
resultsList[2] = "A5";
return super.initState();
}
// Here we're going to create the method which will build and update the dropDown menu
_fieldDropDown(List theList, int resultPosition, var dbField) {
return new FormField(
builder: (FormFieldState state) {
return InputDecorator(
decoration: InputDecoration(),
child: new DropdownButtonHideUnderline(
child: new DropdownButton(
value: this.resultsList[resultPosition],
isDense: true,
onChanged: (dynamic newValue) {
setState(() {
this.resultsList[resultPosition] = newValue;
state.didChange(newValue);
print(
'The List result = ' + this.resultsList[resultPosition]);
//write newValue to a database field, which can be used in the override init to set the field originally
});
},
// Comment to remove Error
items: theList.map((dynamic value) {
return new DropdownMenuItem(
value: value,
child: new Text(value),
);
}).toList(),
),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: Container(
decoration: new BoxDecoration(
color: Color.fromRGBO(0x12, 0x34, 0x55, 0.9),
),
child: new Form(
//key: _formKey,
autovalidate: true,
child: new ListView(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
children: [
Card(
child: Padding(
padding: EdgeInsets.fromLTRB(8, 20, 8, 10),
child: Column(
children: <Widget>[
Text('Informations sur le colis',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18, ),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text('Pli'),
new Radio(
value: 0,
groupValue: null,
onChanged: null
),
new Text('Colis'),
new Radio(
value: 1,
groupValue: null,
onChanged: null
)
]
),
//create some fields, hand in the list
Row(
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width*.3,
child: Text('Départ:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
),
),
Container(
width: MediaQuery.of(context).size.width*.5,
child: _fieldDropDown(_cities, 0, 'colorDBfield')
)
],
),
Row(
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width*.3,
child: Text('Poids:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
),
),
Container(
width: MediaQuery.of(context).size.width*.5,
child: _fieldDropDown(_weight, 1, 'dogDBfield'),
)
],
),
Row(
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width*.3,
child: Text('Dimensions:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15),
),
),
Container(
width: MediaQuery.of(context).size.width*.5,
child: _fieldDropDown(_dimensions, 2, 'peopleDBfield'),
)
],
),
],
)),
),
Card(
child: Padding(
padding: EdgeInsets.fromLTRB(8, 20, 8, 10),
child: Column(
children: <Widget>[
Text('Informations sur le destinataire',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18, ),
),
Row(
children: <Widget>[
new TextField(
decoration: InputDecoration(
border: OutlineInputBorder()
),
),
],
)
],
),
),
)
],
)
),
),
);
}
}
I have a ListView in which I will dynamically add in some children of same type. Inside every children widget has a button. What I want to implement is, that, when user presses the button on a child widget, this child widget will be removed from the ListView. I can do this in C# using events, but I'm a total noob to Dart and Flutter.
Here is my ListView
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('Edit Plan'),
backgroundColor: Colors.green,
actions: <Widget>[
Builder(
builder: (context) => IconButton(
icon: Icon(Icons.add),
onPressed: () {
setState(() {
txts.add('set');
});
},
),
)
],
),
backgroundColor: Colors.white,
body: ListView(
children: txts.map((string) =>
new ListViewItem()).toList(growable: false),
),
);
}
And here is my listViewItem:
class ListViewItem extends StatelessWidget {
final Workout workout;
ListViewItem({Key key, #required this.workout})
: assert(workout != null),
super(key: key);
#override
Widget build(BuildContext context) {
// TODO: implement build
final theme = Theme.of(context);
return Padding(
padding: EdgeInsets.all(12),
child: Card(
elevation: 12,
color: Colors.green,
child: Padding(
padding: EdgeInsets.only(top: 4, bottom: 4, left: 8, right: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const ListTile(
leading: Icon(Icons.album),
title: Text(
'The Enchanted Nightingale',
style: TextStyle(color: Colors.white),
),
subtitle: Text(
'Music by Julie Gable. Lyrics by Sidney Stein.',
style: TextStyle(color: Colors.white),
),
),
TextFormField(
decoration: InputDecoration(
labelText: 'Name your workout',
labelStyle: TextStyle(color: Colors.white)),
),
ButtonTheme.bar(
// make buttons use the appropriate styles for cards
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text(
'DELETE',
style: TextStyle(color: Colors.white),
),
onPressed: () {},
),
],
),
),
],
),
)),
);
}
}
I edited your code to use a ListView.builder, you need to remove the item at index from the List (txts) you are using, your code will be as follows:
List<String> txts = List();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Edit Plan'),
backgroundColor: Colors.green,
actions: <Widget>[
Builder(
builder: (context) =>
IconButton(
icon: Icon(Icons.add),
onPressed: () {
setState(() {
txts.add('set');
});
},
),
)
],
),
backgroundColor: Colors.white,
body: new ListView.builder(
itemCount: txts.length,
itemBuilder: (BuildContext context, int index) {
return ListViewItem(
workout: workout,
onDelete: (){
setState(() {
txts.removeAt(index);
});
},
);
},
),
);
}
in addition to that you need to add an ondelete callback in the ListViewItem, the code in the ListViewItem class will be as follows:
class ListViewItem extends StatelessWidget {
final Workout workout;
final VoidCallback onDelete;
ListViewItem({Key key, #required this.workout, this.onDelete})
: assert(workout != null),
super(key: key);
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding(
padding: EdgeInsets.all(12),
child: Card(
elevation: 12,
color: Colors.green,
child: Padding(
padding: EdgeInsets.only(top: 4, bottom: 4, left: 8, right: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const ListTile(
leading: Icon(Icons.album),
title: Text(
'The Enchanted Nightingale',
style: TextStyle(color: Colors.white),
),
subtitle: Text(
'Music by Julie Gable. Lyrics by Sidney Stein.',
style: TextStyle(color: Colors.white),
),
),
TextFormField(
decoration: InputDecoration(
labelText: 'Name your workout',
labelStyle: TextStyle(color: Colors.white)),
),
ButtonTheme.bar(
// make buttons use the appropriate styles for cards
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text(
'DELETE',
style: TextStyle(color: Colors.white),
),
onPressed: () =>onDelete(),
),
],
),
),
],
),
)),
);
}
}
I've been trying to implement a search bar into my app for bringing selected listView items to the top of a list. The list contains quite a few items, around approximately 1700 so the addition of a search bar is essential. I'd like the listView search box to appear from a search icon on the right hand side of the top appBar. Below is a picture of the current view for reference.
When you click the search iconButton a search field should replace the title in the appBar. It's going to be evident to the user that this is for the crypto listView as I'll add a hint in the search view identifying this.
I'm not including all my code as this would be cumbersome for a stack question, but below is my home_page.dart file, where as the rest of my classes for the bottom crypto listView can be found at this GitHub repo.
This is what my 'home_page.dart` looks like;
import 'package:cryptick/cryptoData/crypto_data.dart';
import 'package:cryptick/cryptoData/trending_data.dart';
import 'package:cryptick/modules/crypto_presenter.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'background.dart';
//FOLLOWING DART CODE COPYRIGHT OF 2017 - 2018 SQUARED SOFTWARE LONDON
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => new _HomePageState();
}
class ServerStatusScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
iconTheme: new IconThemeData(color: Colors.white),
centerTitle: true,
backgroundColor: Colors.black,
title: new Text(
'API Server Status',
textAlign: TextAlign.center,
style: new TextStyle(
color: Colors.white, fontSize: 27.5, fontFamily: 'Kanit'),
),
),
body: new Center(
child: new Column(
children: [
new Divider(color: Colors.white),
new Text(
'News Feed: ',
textAlign: TextAlign.center,
style: new TextStyle(
color: Colors.black,
fontSize: 27.5,
fontFamily: 'Kanit',
),
),
new Divider(),
new Text(
'Crypto Feed: ',
textAlign: TextAlign.center,
style: new TextStyle(
color: Colors.black,
fontSize: 27.5,
fontFamily: 'Kanit',
),
),
new Divider(),
new Wrap(
alignment: WrapAlignment.center,
children: <Widget>[
new Chip(
backgroundColor: Colors.black,
label: new Text(
'© 2017-2018 Squared Software',
style: new TextStyle(
fontSize: 15.0,
fontFamily: 'Poppins',
color: Colors.white,
),
),
),
],
),
],
),
),
);
}
}
class MoreInfoScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
final ThemeData themeData = Theme.of(context);
final TextStyle aboutTextStyle = themeData.textTheme.body2;
final TextStyle linkStyle =
themeData.textTheme.body2.copyWith(color: themeData.accentColor);
return new Scaffold(
appBar: new AppBar(
iconTheme: new IconThemeData(color: Colors.white),
centerTitle: true,
backgroundColor: Colors.black,
title: new Text(
'More Info',
textAlign: TextAlign.center,
style: new TextStyle(
color: Colors.white, fontSize: 27.5, fontFamily: 'Kanit'),
),
),
body: new Center(
child: new Column(
children: [
new Divider(color: Colors.white),
new ListTile(
title: new Text('Squared Software',
style: new TextStyle(
fontWeight: FontWeight.w500,
fontFamily: 'Poppins',
)
),
leading: new CircleAvatar(
radius: 30.0,
backgroundImage: new AssetImage(
'images/sqinterlock.png'
)
)
),
new Divider(),
new Text('Where do we get our information?',
style: new TextStyle(
color: Colors.black,
fontFamily: 'Poppins',
fontSize: 16.5,
)
),
new Divider(color: Colors.white),
new Text(
"News Feed: bit.ly/2MFpzHX",
style: new TextStyle(
fontFamily: 'Poppins',
fontSize: 16.5,
),
),
new Divider(color: Colors.white),
new Text(
"Crypto Feed: bit.ly/2iIdJht",
style: new TextStyle(
fontFamily: 'Poppins',
fontSize: 16.5,
),
),
new Divider(color: Colors.white),
new Wrap(
alignment: WrapAlignment.center,
children: <Widget>[
new Chip(
backgroundColor: Colors.black,
label: new Text(
'© 2017-2018 Squared Software',
style: new TextStyle(
fontSize: 15.0,
fontFamily: 'Poppins',
color: Colors.white,
),
),
),
],
),
],
),
),
);
}
}
class _HomePageState extends State<HomePage> implements CryptoListViewContract {
CryptoListPresenter _presenter;
List<Crypto> _currencies;
bool _isLoading;
final List<MaterialColor> _colors = [Colors.blue, Colors.indigo, Colors.red];
_HomePageState() {
_presenter = new CryptoListPresenter(this);
}
#override
void onLoadTrendingComplete(Trending trending) {
// TODO:
articlesMap = trending.articles;
for (Map articleMap in articlesMap) {
articles.add(Articles.fromMap(articleMap));
}
if (mounted) setState(() {});
}
#override
void onLoadTrendingError() {
// TODO:
}
List articlesMap = [];
List<Articles> articles = [];
#override
void initState() {
super.initState();
_isLoading = true;
_presenter.loadCurrencies();
_presenter.loadTrending();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(
"Cryp - Tick Exchange",
style: new TextStyle(
color: Colors.white,
fontFamily: 'Poppins',
fontSize: 22.5,
),
),
iconTheme: new IconThemeData(color: Colors.white),
backgroundColor: const Color(0xFF273A48),
elevation: 0.0,
centerTitle: true,
),
drawer: new Drawer(
child: new ListView(padding: EdgeInsets.zero, children: <Widget>[
new DrawerHeader(
child: new CircleAvatar(
child: new Image.asset('images/ctavatar.png'),
),
decoration: new BoxDecoration(
color: Colors.black,
),
),
new MaterialButton(
child: new Text(
'Server Status',
textAlign: TextAlign.center,
style: new TextStyle(fontSize: 27.5, fontFamily: 'Kanit'),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ServerStatusScreen()),
);
}),
new Divider(),
new MaterialButton(
child: new Text(
'More Info',
textAlign: TextAlign.center,
style: new TextStyle(fontSize: 27.5, fontFamily: 'Kanit'),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MoreInfoScreen()),
);
}),
new Divider(),
new Wrap(
alignment: WrapAlignment.center,
children: <Widget>[
new Chip(
backgroundColor: Colors.black,
label: new Text(
'v0.0.1',
style: new TextStyle(
fontSize: 15.0,
fontFamily: 'Poppins',
color: Colors.white,
),
),
),
],
),
]),
),
body: _isLoading
? new Center(child: new CupertinoActivityIndicator(radius: 15.0))
: _allWidget());
}
Widget _allWidget() {
final _width = MediaQuery.of(context).size.width;
final _height = MediaQuery.of(context).size.height;
//CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED
final headerList = new ListView.builder(
itemBuilder: (context, index) {
EdgeInsets padding = index == 0
? const EdgeInsets.only(
left: 20.0, right: 10.0, top: 4.0, bottom: 30.0)
: const EdgeInsets.only(
left: 10.0, right: 10.0, top: 4.0, bottom: 30.0);
return new Padding(
padding: padding,
child: new InkWell(
onTap: () {
print('#url');
},
child: new Container(
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(10.0),
color: const Color(0xFF273A48),
boxShadow: [
new BoxShadow(
color: Colors.black.withAlpha(70),
offset: const Offset(3.0, 10.0),
blurRadius: 15.0)
],
image: new DecorationImage(
image: new NetworkImage(articles[index].urlToImage),
fit: BoxFit.fitHeight,
),
),
height: 200.0,
width: 275.0,
child: new Stack(
children: <Widget>[
new Align(
alignment: Alignment.bottomCenter,
child: new Container(
padding: new EdgeInsets.only(left: 10.0),
decoration: new BoxDecoration(
color: const Color(0xFF273A48),
borderRadius: new BorderRadius.only(
bottomLeft: new Radius.circular(10.0),
bottomRight: new Radius.circular(10.0)),
),
height: 50.0,
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Expanded(child: new Text(
articles[index].title,
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: new TextStyle(
color: Colors.white,
fontFamily: 'Poppins',
),
),
),
],
)
),
)
],
),
),
),
);
},
scrollDirection: Axis.horizontal,
itemCount: articles.length,
);
final body = new Scaffold(
backgroundColor: Colors.transparent,
body: new Container(
child: new Stack(
children: <Widget>[
new Padding(
padding: new EdgeInsets.only(top: 10.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Align(
alignment: Alignment.centerLeft,
child: new Padding(
padding: new EdgeInsets.only(
left: 10.0,
),
child: new Text(
"Trending News",
style: new TextStyle(
letterSpacing: 0.8,
fontFamily: 'Kanit',
fontSize: 17.5,
color: Colors.white,
),
)),
),
new Container(
height: 300.0, width: _width, child: headerList),
new Expanded(child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
final int i = index;
final Crypto currency = _currencies[i];
final MaterialColor color = _colors[i % _colors.length];
return new ListTile(
title: new Column(
children: <Widget>[
new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
height: 72.0,
width: 72.0,
decoration: new BoxDecoration(
color: Colors.white,
boxShadow: [
new BoxShadow(
color: Colors.black.withAlpha(80),
offset: const Offset(2.0, 2.0),
blurRadius: 15.0)
],
borderRadius: new BorderRadius.all(
new Radius.circular(35.0)),
image: new DecorationImage(
image: new ExactAssetImage(
"cryptoiconsBlack/" +
currency.symbol.toLowerCase() +
"#2x.png",
),
fit: BoxFit.cover,
)),
),
new SizedBox(
width: 8.0,
),
new Expanded(
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
currency.name,
style: new TextStyle(
fontSize: 15.0,
fontFamily: 'Poppins',
color: Colors.black87,
fontWeight: FontWeight.bold),
),
_getSubtitleText(currency.price_usd,
currency.percent_change_1h),
],
)),
],
),
new Divider(),
],
),
);
}))
],
),
),
],
),
),
);
return new Container(
decoration: new BoxDecoration(
color: const Color(0xFF273A48),
),
child: new Stack(
children: <Widget>[
new CustomPaint(
size: new Size(_width, _height),
painter: new Background(),
),
body,
],
),
);
}
// CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED
Widget _getSubtitleText(String priceUSD, String percentageChange) {
TextSpan priceTextWidget = new TextSpan(
text: "\$$priceUSD\n",
style: new TextStyle(
color: Colors.black,
fontSize: 14.0,
));
String percentageChangeText = "1 hour: $percentageChange%";
TextSpan percentageChangeTextWidget;
if (double.parse(percentageChange) > 0) {
percentageChangeTextWidget = new TextSpan(
text: percentageChangeText,
style: new TextStyle(
color: Colors.green,
fontFamily: 'PoppinsMediumItalic',
));
} else {
percentageChangeTextWidget = new TextSpan(
text: percentageChangeText,
style: new TextStyle(
color: Colors.red,
fontFamily: 'PoppinsMediumItalic',
));
}
return new RichText(
text: new TextSpan(
children: [priceTextWidget, percentageChangeTextWidget]));
}
//Works with cryptoListViewContract implimentation in _MyHomePageState
#override
void onLoadCryptoComplete(List<Crypto> items) {
// TODO: implement onLoadCryptoComplete
setState(() {
_currencies = items;
_isLoading = false;
});
}
#override
void onLoadCryptoError() {
// TODO: implement onLoadCryptoError
}
}
Thanks for the help, Jake
There are probably many ways to implement this based on the resulting experience you want. A simple solution is to create activeSearch state that toggles a 'search app bar' and a 'normal app bar'
Here's the normal app bar:
return AppBar(
title: Text("My App"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () => setState(() => activeSearch = true),
),
],
);
And here's the search app bar:
return AppBar(
leading: Icon(Icons.search),
title: TextField(
decoration: InputDecoration(
hintText: "here's a hint",
),
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () => setState(() => activeSearch = false),
)
],
);
Note: if you don't want to have a leading icon when search is active you may want to disable the default behavior for a drawer and back button icon with:
automaticallyImplyLeading: false
Full example:
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool activeSearch;
#override
void initState() {
super.initState();
activeSearch = false;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appBar(),
drawer: _drawer(),
);
}
PreferredSizeWidget _appBar() {
if (activeSearch) {
return AppBar(
leading: Icon(Icons.search),
title: TextField(
decoration: InputDecoration(
hintText: "here's a hint",
),
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () => setState(() => activeSearch = false),
)
],
);
} else {
return AppBar(
title: Text("My App"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () => setState(() => activeSearch = true),
),
],
);
}
}
Widget _drawer() {
return Container();
}
}
UPDATE: Here's a hint at handling results
return AppBar(
...
title: TextField(
onChanged: _search,
),
);
And what _search could look like:
List<MyResultObject> _results;
void _search(String queryString) {
// do some searching and sorting
// then call setState() with the results
// and then in your ListView you can read from results
// (handle empty, default case as well in view)
setState(() {
_results = ...
});
}
List<Widget> _resultWidgets() {
if (_results.isEmpty) return _defaultWidgets();
_results.map((r) => _buildRowWidget(s)).toList();
}
Can u refer a simple search view in this answer. In that example, as the user types, the list will get filtered.