bottom sheet is showing repeatedly once event triggred using bloc - flutter

I am a newbie, learning how to use bloc with freezed. i created a bottom sheet when the user click to the float action button, the bottom sheet appears. Bottom sheet contains text field and three radio groups when i click to select a radio the bottom sheet popup again like this GIF.
https://drive.google.com/file/d/1iU06adGcwjEaw9z2LsmO5xS24CC6OQfT/view?usp=sharing
the bloc is:
class NoteBloc extends Bloc<NoteEvent, NoteState> {
NoteBloc() : super(NoteState.initial());
#override
Stream<NoteState> mapEventToState(
NoteEvent event,
) async* {
yield* event.map(
addNewNoteButtonEvent: (AddNewNoteButtonEvent e) async* {
yield state.copyWith(
isAddNewNoteButtonClickedState: e.isClickedEvent,
);
},
radioEvent: (RadioEvent e) async* {
yield state.copyWith(radioGroupState: e.value);
},
textInputEvent: (TextInputEvent e) async* {
yield state.copyWith(textInputState: Note(value: e.value));
},
);
}
}
the event is:
class NoteEvent with _$NoteEvent {
const factory NoteEvent.addNewNoteButtonEvent(
{required bool isClickedEvent}) = AddNewNoteButtonEvent;
const factory NoteEvent.radioEvent({required int value}) = RadioEvent;
const factory NoteEvent.textInputEvent({required String value}) =
TextInputEvent;
}
the state is:
class NoteState with _$NoteState {
const factory NoteState({
// required bool showSaveIconState,
required int radioGroupState,
required bool isAddNewNoteButtonClickedState,
required Note textInputState,
}) = _NoteState;
// initialize every state
factory NoteState.initial() => NoteState(
radioGroupState: 1,
isAddNewNoteButtonClickedState: false,
textInputState: Note(value: ''),
);
}
the home page code is:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
return BlocConsumer<NoteBloc, NoteState>(
listener: (context, state) {
// if (state is AddNoteClickedState) {
if (state.isAddNewNoteButtonClickedState) {
// show navigator
_scaffoldKey.currentState!
.showBottomSheet((context) => const AddNewNoteBottomSheet())
.closed
.then(
(value) {
// Change state
// to tell the bloc that
// the bottom sheet is closed
context.read<NoteBloc>().add(
const NoteEvent.addNewNoteButtonEvent(
isClickedEvent: false, // !state.isAddNewNoteClickedState,
),
);
},
);
}
},
builder: (context, state) {
return DefaultTabController(
length: 3,
child: Scaffold(
key: _scaffoldKey,
// add note float action button
floatingActionButton: InkWell(
onTap: () {
// make button clicked
// to open the bottom sheet
context.read<NoteBloc>().add(
const NoteEvent.addNewNoteButtonEvent(
isClickedEvent: true, //!state.isAddNewNoteClickedState,
),
);
// print(state);
},
// for float action button
// icon shape
child: Container(
clipBehavior: Clip.antiAliasWithSaveLayer,
height: 11.h,
width: 10.h,
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(1.w)),
color: const Color(accentColor),
),
// add note icon
child: Icon(
state.isAddNewNoteButtonClickedState ? Icons.save : Icons.add,
color: const Color(whiteColor),
size: 9.h,
),
),
);
},
);
}
}
AddNewNoteBottomSheet code is:
// For adding new note
class AddNewNoteBottomSheet extends StatelessWidget {
const AddNewNoteBottomSheet();
#override
Widget build(BuildContext context) {
TextEditingController _textEditingController = TextEditingController();
return Container(
// Contains all
// bottom sheet components
child: Column(
children: [
// bottom sheet body
Expanded(
child: SingleChildScrollView(
child: BlocBuilder<NoteBloc, NoteState>(
builder: (context, state) {
// print("Helllo ${state.maybeMap(orElse: (){},radioClickState: (stat)=>stat.value)}");
return Column(
children: [
// Today Radio
ListTile(
title: Text(
AppLocalizations.of(context)!.homeTodayTapTitle,
style: Theme.of(context)
.tabBarTheme
.unselectedLabelStyle,
),
contentPadding: const EdgeInsets.all(0),
autofocus: true,
leading: Radio<int>(
value: 1,
//get group value
groupValue: state.radioGroupState,
onChanged: (value) {
// tell bloc i click you
context
.read<NoteBloc>()
.add(NoteEvent.radioEvent(value: value!));
},
),
),
// Tomorrow Radio
ListTile(
contentPadding: const EdgeInsets.all(0),
title: Text(
AppLocalizations.of(context)!.homeTomorrowTapTitle,
style: Theme.of(context)
.tabBarTheme
.unselectedLabelStyle,
),
leading: Radio<int>(
value: 2,
//get group value
groupValue: state.radioGroupState,
onChanged: (value) {
// tell bloc i click you
context.read<NoteBloc>().add(
NoteEvent.radioEvent(value: value!),
);
},
),
),
// Some Day Radio
ListTile(
contentPadding: const EdgeInsets.all(0),
title: Text(
AppLocalizations.of(context)!.homeSomeDayTapTitle,
style: Theme.of(context)
.tabBarTheme
.unselectedLabelStyle,
),
leading: Radio<int>(
value: 3,
//get group value
groupValue: state.radioGroupState,
onChanged: (value) {
// tell bloc i click you
context.read<NoteBloc>().add(
NoteEvent.radioEvent(value: value!),
);
},
),
),
],
);
},
),
),
),
],
),
);
}
}

Problem definition: This problem happens because you use state.copyWith on your bloc. When you use copyWith, even if you don't update isAddNewNoteButtonClickedState variable, your state persists that value (stays true after you set it, never changes if you don't change it) if you do not alter it. Because on copyWith method, update logic works like;
YourState copyWith(bool isAddNewNoteButtonClickedState) {
return YourState(isAddNewNoteButtonClickedState: isAddNewNoteButtonClickedState ?? this. isAddNewNoteButtonClickedState,)
}
And when you yield another state, this isAddNewNoteButtonClickedState stays true, and your listener shows another modal since your isAddNewNoteButtonClickedState did not change.
Solution: you can solve this problem by (adding isAddNewNoteButtonClickedState: false to other states):
class NoteBloc extends Bloc<NoteEvent, NoteState> {
NoteBloc() : super(NoteState.initial());
#override
Stream<NoteState> mapEventToState(
NoteEvent event,
) async* {
yield* event.map(
addNewNoteButtonEvent: (AddNewNoteButtonEvent e) async* {
yield state.copyWith(
isAddNewNoteButtonClickedState: e.isClickedEvent,
);
},
radioEvent: (RadioEvent e) async* {
yield state.copyWith(radioGroupState: e.value, isAddNewNoteButtonClickedState: false);
},
textInputEvent: (TextInputEvent e) async* {
yield state.copyWith(textInputState: Note(value: e.value), isAddNewNoteButtonClickedState: false);
},
);
}
}
Additional note:
Since you are not transforming events in bloc, or doing something trivial, you can use cubit to simplify your state management and business logic.

Related

How to send a data from listview screen to form screen using flutter

I am trying to send a data from ontap listview screen to form screen like image below. I have searched many references on google but I can't find any references that can help me, if you can provide solutions or references, I will greatly appreciate it.
enter image description here
This is my sample code (ListPage Screen) :
const ListPage({Key? key}) : super(key: key);
#override
State<ListPage> createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
TextEditingController textFieldController = TextEditingController();
var _controller = TextEditingController();
late bool searching, error;
var data;
late String query;
String dataurl = "https://www.something.co.id/mobile/search_data.php";
#override
void initState() {
searching = true;
error = false;
query = "";
super.initState();
}
void getSuggestion() async {
//get suggestion function
var res = await http
.post((Uri.parse(dataurl + "?query=" + Uri.encodeComponent(query))));
//in query there might be unwant character so, we encode the query to url
if (res.statusCode == 200) {
setState(() {
data = json.decode(res.body);
//update data value and UI
});
} else {
//there is error
setState(() {
error = true;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: AppLayout.getHeight(100),
automaticallyImplyLeading: false,
title: searchField(),
backgroundColor: Styles.background,
elevation: 0.0,
),
body: SingleChildScrollView(
child: Container(
alignment: Alignment.center,
child: data == null
? Container(
padding: EdgeInsets.all(20),
child: searching
? Text("Please wait")
: Text("Search any location")
//if is searching then show "Please wait"
//else show search peopels text
)
: Container(
child: searching
? showSearchSuggestions()
: Text("Find any location"),
)
// if data is null or not retrived then
// show message, else show suggestion
),
),
);
}
Widget showSearchSuggestions() {
List suggestionlist = List.from(data["data"].map((i) {
return SearchSuggestion.fromJSON(i);
}));
//serilizing json data inside model list.
return Column(
children: suggestionlist.map((suggestion) {
return InkResponse(
// onTap: () {
// //when tapped on suggestion
// print(suggestion.id); //pint student id
// },
child: GestureDetector(
onTap: () {
_sendDataBack(context);
},
child: SizedBox(
width: double.infinity, //make 100% width
child: Card(
child: Container(
decoration: BoxDecoration(color: Styles.background),
padding: EdgeInsets.all(15),
child: Text(
suggestion.name,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
);
}).toList(),
);
}
// get the text in the TextField and send it back to the FirstScreen
void _sendDataBack(BuildContext context) {
String textToSendBack = textFieldController.text;
Navigator.pop(context, textToSendBack);
}
Widget searchField() {
//search input field
return Container(
height: 50,
child: TextField(
controller: _controller,
autofocus: true,
style: Styles.textStyle,
decoration: InputDecoration(
hintStyle: TextStyle(color: Styles.colorDeepGrey),
hintText: "Search Location...",
prefixIcon: Icon(Icons.search),
suffixIcon: _controller.text.length > 0
? IconButton(
onPressed: () {
_controller.clear();
setState(() {});
},
icon: Icon(Icons.cancel, color: Colors.grey))
: null,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Styles.colorLightBlack.withOpacity(0.20),
width: 2,
),
borderRadius: BorderRadius.circular(4),
), //under line border, set OutlineInputBorder() for all side border
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Styles.primaryColor,
width: 1,
),
borderRadius: BorderRadius.circular(4),
), // focused border color
), //decoration for search input field
onChanged: (value) {
query = value; //update the value of query
getSuggestion(); //start to get suggestion
},
),
);
}
}
//serarch suggestion data model to serialize JSON data
class SearchSuggestion {
String id, name;
SearchSuggestion({required this.id, required this.name});
factory SearchSuggestion.fromJSON(Map<String, dynamic> json) {
return SearchSuggestion(
id: json["id"],
name: json["name"],
);
}
}
Sample Code NextPage Screen :
class NextPage extends StatefulWidget {
#override
_NextPageState createState() => _NextPageState();
}
class _NextPageState extends State<NextPage> {
int _currentStep = 0;
StepperType stepperType = StepperType.vertical;
String text = 'Text';
var _controller = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text('Flutter Stepper Demo'),
centerTitle: true,
),
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Stepper(
type: stepperType,
physics: ScrollPhysics(),
currentStep: _currentStep,
onStepTapped: (step) => tapped(step),
onStepContinue: continued,
onStepCancel: cancel,
steps: <Step>[
//Form Pengirim
Step(
title: new Text('Location'),
content: Column(
children: <Widget>[
SizedBox(
height: 50,
child: TextField(
onTap: () {
_awaitReturnValueFromSecondScreen(context);
},
controller: _controller,
autofocus: true,
onChanged: (text) {
setState(() {});
},
style: Styles.textStyle,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
hintText: 'Location',
contentPadding:
EdgeInsets.only(left: 15, right: 15),
hintStyle: TextStyle(color: Styles.colorDeepGrey),
suffixIcon: _controller.text.length > 0
? IconButton(
onPressed: () {
_controller.clear();
setState(() {});
},
icon: Icon(Icons.cancel,
color: Colors.grey))
: null,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color:
Styles.colorLightBlack.withOpacity(0.20),
width: 2,
),
borderRadius: BorderRadius.circular(4),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Styles.primaryColor,
width: 1,
),
borderRadius: BorderRadius.circular(4),
),
),
),
),
],
),
isActive: _currentStep >= 0,
state: _currentStep >= 0
? StepState.complete
: StepState.disabled,
),
],
),
),
],
),
),
);
}
void _awaitReturnValueFromSecondScreen(BuildContext context) async {
// start the SecondScreen and wait for it to finish with a result
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DataAlamat(),
));
// after the SecondScreen result comes back update the Text widget with it
setState(() {
text = result;
});
}
tapped(int step) {
setState(() => _currentStep = step);
}
continued() {
_currentStep < 2 ? setState(() => _currentStep += 1) : null;
}
cancel() {
_currentStep > 0 ? setState(() => _currentStep -= 1) : null;
}
}
Pass the tapped item value to the next page via named parameter of other page class.
class ListPage extends StatelessWidget {
const ListPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return ListTile(
onTap: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return NextPage(value: index);
},
));
},
title: Text(index.toString()),
);
},
),
);
}
}
class NextPage extends StatelessWidget {
final int value;
const NextPage({Key? key, required this.value}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(value.toString()),
),
);
}
}
Example in ListView screen, you have a variable called List<String> listLocations. Your ListView widget be like:
ListView.builder(
itemCount: listLocations.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return SecondScreen(listLocations[index]);
},
)),
child: ...
);
}
}
And your SecondScreen is a StatefulWidget (well it is a Form screen, so it would be Stateful, not Stateless, use TextEditingController in Form widget):
import 'package:flutter/material.dart';
class SecondScreen extends StatefulWidget {
final String location;
SecondScreen(this.location, {Key? key}) : super(key: key);
#override
State<SecondScreen> createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
var _textEditingController = TextEditingController();
#override
void initState() {
_textEditingController.text = widget.location;
super.initState();
}
#override
void dispose() {
_textEditingController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
You need to pass the location value in init state, and don't forget to dispose it.

Keep dropdown open when active checkbox

I have a code that is responsible for filtering by certain categories (I shortened it for ease of reading). When opening the filter window, the user sees these category names ('Select a brand', 'Select a operation system', 'Select a color' etc).
Next, the user can open the category (initially, the dropdown list is in the closed position.), and select the parameters from the drop-down list (and click the apply button). The next time you open the filter window, the checkboxes in front of the parameters remain, but the drop-down list collapses.
Tell me how to do it: if in any category there are options marked with a checkmark, so that the drop-down list will be open the next time the window with filters is opened.
class FilterDialog extends StatefulWidget {
final void Function(Map<String, List<String>?>) onApplyFilters;
final Map<String, List<String>?> initialState;
const FilterDialog({
Key? key,
required this.onApplyFilters,
this.initialState = const {},
}) : super(key: key);
#override
State<FilterDialog> createState() => _FilterDialogState();
}
class _FilterDialogState extends State<FilterDialog> {
// Temporary storage of filters.
Map<String, List<String>?> filters = {};
bool needRefresh = false;
// Variable for the ability to hide all elements of filtering by any parameter.
bool isClickedBrand = false;
List manufacturer = [];
#override
void initState() {
super.initState();
filters = widget.initialState;
}
// A function to be able to select an element to filter.
void _handleCheckFilter(bool checked, String key, String value) {
final currentFilters = filters[key] ?? [];
if (checked) {
currentFilters.add(value);
} else {
currentFilters.remove(value);
}
setState(() {
filters[key] = currentFilters;
});
}
// Building a dialog box with filters.
#override
Widget build(BuildContext context) {
return SimpleDialog(
// Window title.
title: const Text('Filters',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.w600,
)),
contentPadding: const EdgeInsets.all(16),
// Defining parameters for filtering.
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Here and in subsequent Column, there will be a definition of parameters for filtering,
// a title, the ability to hide/show the items of list
Column(children: [
InkWell(
onTap: () async {
manufacturer = await getManufacturerOptions();
setState(() {
isClickedBrand = !isClickedBrand;
});
},
child: Row(children: [
Text('Select a brand'.toString(),
style: const TextStyle(
fontSize: 18,
)),
const Spacer(),
isClickedBrand
? const Icon(Icons.arrow_circle_up)
: const Icon(Icons.arrow_circle_down)
])),
!isClickedBrand
? Container()
: Column(
children: manufacturer
.map(
(el) => CustomCheckboxTile(
value: filters['manufacturer']?.contains(el) ??
false,
label: el,
onChange: (check) =>
_handleCheckFilter(check, 'manufacturer', el),
),
)
.toList())
]),
const SizedBox(
height: 5,
),
// Building a button to apply parameters.
const SizedBox(
height: 10,
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
widget.onApplyFilters(filters);
needRefresh = true;
},
child:
const Text('APPLY', style: TextStyle(color: Colors.black)),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.grey),
)),
// Building a button to reset parameters.
const SizedBox(
height: 5,
),
ElevatedButton(
onPressed: () async {
setState(() {
filters.clear();
});
widget.onApplyFilters(filters);
},
child: const Text('RESET FILTERS',
style: TextStyle(color: Colors.black)),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.grey),
)),
],
),
],
);
}
}
For example: the user clicks on the filter box, selects the brands to search for, and clicks the apply button. My task is that the next time the user opens the filter window, the categories with active checkboxes (in this example, the brand) are in an expanded state
The concept is, you need to check filter data with while opening dialog, To simplify the process I am using ExpansionTile. You can check this demo and customize the behavior and look.
Run on dartPad, Click fab to open dialog and touch outside the dialog to close this.
class ExTExpample extends StatefulWidget {
ExTExpample({Key? key}) : super(key: key);
#override
State<ExTExpample> createState() => _ExTExpampleState();
}
class _ExTExpampleState extends State<ExTExpample> {
// you can use map or model class or both,
List<String> filter_data = [];
List<String> brands = ["Apple", "SamSung"];
List<String> os = ["iOS", "Android"];
_showFilter() async {
await showDialog(
context: context,
builder: (c) {
// you can replace [AlertDialog]
return AlertDialog(
content: StatefulBuilder(
builder: (context, setSBState) => SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ExpansionTile(
title: const Text("Brand"),
/// check any of its's item is checked or not
initiallyExpanded: () {
// you can do different aproach
for (final f in brands) {
if (filter_data.contains(f)) return true;
}
return false;
}(),
children: [
...brands.map(
(brandName) => CheckboxListTile(
value: filter_data.contains(brandName),
title: Text(brandName),
onChanged: (v) {
if (filter_data.contains(brandName)) {
filter_data.remove(brandName);
} else {
filter_data.add(brandName);
}
setSBState(() {});
//you need to reflect the main ui, also call `setState((){})`
},
),
),
],
),
ExpansionTile(
title: const Text("select OS"),
/// check any of its's item is checked or not
initiallyExpanded: () {
// you can do different aproach
for (final f in os) {
if (filter_data.contains(f)) return true;
}
return false;
}(),
children: [
...os.map(
(osName) => CheckboxListTile(
value: filter_data.contains(osName),
title: Text(osName),
onChanged: (v) {
if (filter_data.contains(osName)) {
filter_data.remove(osName);
} else {
filter_data.add(osName);
}
setSBState(() {});
//you need to reflect the main ui, also call `setState((){})`
},
),
),
],
),
],
),
),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FloatingActionButton(
onPressed: () {
_showFilter();
},
),
),
);
}
}
Whenever you open filter the isClickedBrand is False so it won't showed you a list. So the solution is :
After selecting option from list, change the state of isClickedBrand state. I mean if it's true then it will show the list otherwise show container.
Hope you get my point.

Open Flutter dropdown programmatically

I am trying to build a searchable dropdown which will load value from service on every button click. So, for that I have encapsulated DropDownButton and TextField in Stack Widget.
On keypress we get response from api, so far so good. But after getting data from api dropdown was not opening. After digging a bit I came to know it was not opening because we need to manually tap it to open, but since its in stack and second children is TextField I can't tap it.
But opening DropDownButton button programmatically is not possible.
So I tried second solution from https://stackoverflow.com/a/59499191/10423593 but it didn't work.
Below is my code without solution from stackoverflow.
import 'package:flutter/material.dart';
import 'package:giphy/services/gifs_service.dart';
import 'package:giphy/shared/autocomplete.dart';
class TestDropDown extends StatefulWidget {
// const TestDropDown({Key? key}) : super(key: key);
#override
_TestDropDownState createState() => _TestDropDownState();
}
class _TestDropDownState extends State<TestDropDown> {
final GifyService _service = GifyService();
final TextEditingController _gifSearchController = TextEditingController();
List<SearchData> _dropDownResult = <SearchData>[];
GlobalKey key = GlobalKey();
// T? _findChildWidgetFromKey<T extends Widget>(
// BuildContext? context, T childWidget) {
// T? detector;
// context!.visitChildElements((element) {
// if (element.widget == childWidget) {
// detector = element.widget as T;
// }
// });
// return detector;
// }
Widget _buildDropDown(List<SearchData> searchData) => DropdownButton<String>(
isExpanded: true,
key: key,
onChanged: (String? value) {},
items: searchData
.map(
(e) => DropdownMenuItem<String>(
child: Text(e.name ?? ''),
value: e.name ?? '',
),
)
.toList(),
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
_buildDropDown(_dropDownResult),
Container(child: () {
if (_dropDownResult.length > 0) {
_buildDropDown(_dropDownResult);
}
}()),
TextField(
controller: _gifSearchController,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(width: 0.5),
borderRadius: BorderRadius.circular(21),
),
),
onChanged: (String value) async {
AutoComplete result = await _service.getSearchKeywords(value);
setState(() {
_dropDownResult = result.data;
});
},
),
],
),
);
}
}
If you are looking to autocomplete the text field then you can use
autocomplete_textfield package.
Or if you want to build it on your own then you can try a different approach by using container instead of dropdown menu.
After trying some packages. I wasn't able to do flutter autocomplete based on api calls.
So decided to try some other approach and used Overlay in flutter
import 'dart:async';
import 'package:flutter/material.dart';
class TestDropDown extends StatefulWidget {
#override
_TestDropDownState createState() => _TestDropDownState();
}
class _TestDropDownState extends State<TestDropDown> {
late OverlayEntry _overlayEntry;
Timer? _debounce;
_showOverlay(BuildContext context) {
OverlayState? state = Overlay.of(context);
final RenderBox? box = key.currentContext!.findRenderObject() as RenderBox;
Size size = box!.size;
Offset position = box.localToGlobal(Offset.zero);
_overlayEntry = OverlayEntry(
builder: (context) => Positioned(
top: size.height + position.dy,
left: position.dx,
child: Card(
child: Container(
height: 200,
width: size.width,
decoration: BoxDecoration(
border: Border.all(),
),
child: Column(
children: [
Container(
width: size.width,
child: IconButton(
onPressed: () {
if (_overlayEntry.mounted) {
_overlayEntry.remove();
}
},
icon: Icon(Icons.close),
alignment: Alignment.topRight,
),
),
Expanded(
child: ListView(
children: []..addAll(List.generate(
200, (index) => Text(index.toString()))),
),
),
],
)),
),
),
);
state!.insert(_overlayEntry);
}
final key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(50),
child: Column(
children: [
TextField(
key: key,
onChanged: (String searchText) {
if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 500), () {
print(searchText);
});
},
),
ElevatedButton(
onPressed: () {
_showOverlay(context);
},
child: Text('Press Me'),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
if (_overlayEntry.mounted) {
_overlayEntry.remove();
}
},
),
);
}
}
try using: https://pub.dev/packages/dropdown_search
Step 1: you request all data
Step 2: using the library you can searching item

I am trying to create list of TextFormFields which takes numbers as inputs and I want to Sum all those numbers

I am trying to create list of TextFormFields which takes numbers as inputs and I want to Sum all those numbers. When I click on a button on app bar new textformfield appears and user enters value..validator is also working fine...But I am not able to do the Sum. When I used print in Onsaved method it displays all the entered values..If I use Controller, whatever the text we enter in formfield it is displaying same same in all the other textfields also..so controller is not working...I created TextFormField in different function and calling that function when button is pressed. I created another button to go to next screen at the same time to validate which works fine...
Below is the TextFormField code: Please help to Sum all the values entered in it:
child: TextFormField(
// controller: _childController,
decoration: InputDecoration(
hintText: 'Value $_count',
border: InputBorder.none,
contentPadding: EdgeInsets.only(top: 5, left: 20)),
keyboardType: TextInputType.number,
style: TextStyle(
color: Color.fromARGB(255, 0, 0, 0),
fontWeight: FontWeight.w400,
fontSize: 24,
),
validator: (String value) {
double sal = double.tryParse(value);
if (sal == null) {
return 'enter or delete row';
}
},
onSaved: (String value) {
// print(_childController.text);
// print(value);
_mVal = value;
double _mVal2 = double.tryParse(_mVal);
double _mVal3;
print(_mVal);
int k = 0;
_children.forEach((element) {
int y = int.tryParse(_mVal);
k=k+y;
print(k);
}
Here is a quick example of how you can achieve this:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Test(),
),
);
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
final _formKey = GlobalKey<FormState>();
List<TextEditingController> textFieldControllers = [];
int numberOfTextFields = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
addNewTextField();
},
),
body: Stack(
children: [
Form(
key: _formKey,
child: ListView.builder(
itemCount: numberOfTextFields,
itemBuilder: (BuildContext context, int index) {
return TextFormField(
validator: (String value) {
double sal = double.tryParse(value);
if (sal == null) {
return 'enter or delete row';
}
return null;
},
controller: textFieldControllers[index],
);
},
),
),
Align(
alignment: Alignment.bottomCenter,
child: TextButton(
onPressed: () {
if (_formKey.currentState.validate()) {
showDialog(
context: context,
builder: (BuildContext context) {
return Center(
child: Material(
child: Container(
padding: EdgeInsets.all(10.0),
child: Text(
'The sum is ${textFieldControllers.fold(0, (previousValue, element) => previousValue + int.parse(element.value.text))}'),
),
),
);
});
}
},
child: Container(
padding: EdgeInsets.all(10.0),
color: Colors.redAccent,
child: Text('Tap to sum'),
),
),
),
],
),
);
}
void addNewTextField() {
textFieldControllers.add(TextEditingController());
numberOfTextFields++;
setState(() {});
}
#override
void dispose() {
textFieldControllers.forEach((textFieldController) => textFieldController.dispose());
super.dispose();
}
}
You can expand on this idea to remove textField if needed. Just don't forget to dispose your textFields.
How does this work: Each time a TextField Widget is create, an associated TextEditingController is created and given to the TextField. When we want to sum, we just iterate on the TextEditingController list.

Flutter search bar with autocomplete

I'm looking for a search bar in flutter docs but can't find it, is there a widget for the search bar with autocomplete in appbar. For example, I have a search icon on my appbar. When one press it show's the search box, when you type it should show autocomplete from the dropdown with listtile. I managed to implement this but it's not easy to use because I need a dropdown to show suggestion autocomplete, then use the suggestion for a new route if selected.
Here the search action
You can use Stack to achieve the autocomplete dropdown box effect. Example below has 2 Containers - both hold ListView as child objects. One holds search results, other has some random text as content for the body. ListView (search result) is placed inside an Align Object and alignment property is set to Alignment.topCenter. This ensures that List appears at the top, just below the AppBar.
Updated the Post (accepted answer) mentioned in the comments for a complete a demo.
As explained above:
#override
Widget build(BuildContext context) {
return new Scaffold(
key: key,
appBar: buildBar(context),
body: new Stack(
children: <Widget>[
new Container(
height: 300.0,
padding: EdgeInsets.all(10.0),
child: new DefaultTabController(length: 5, child: mainTabView),
),
displaySearchResults(),
],
));
}
Widget displaySearchResults() {
if (_IsSearching) {
return new Align(
alignment: Alignment.topCenter,
//heightFactor: 0.0,
child: searchList());
} else {
return new Align(alignment: Alignment.topCenter, child: new Container());
}
}
Complete demo
class SearchList extends StatefulWidget {
SearchList({Key key, this.name}) : super(key: key);
final String name;
#override
_SearchListState createState() => new _SearchListState();
}
class _SearchListState extends State<SearchList> {
Widget appBarTitle = new Text(
"",
style: new TextStyle(color: Colors.white),
);
Icon actionIcon = new Icon(
Icons.search,
color: Colors.white,
);
final key = new GlobalKey<ScaffoldState>();
final TextEditingController _searchQuery = new TextEditingController();
List<SearchResult> _list;
bool _IsSearching;
String _searchText = "";
String selectedSearchValue = "";
_SearchListState() {
_searchQuery.addListener(() {
if (_searchQuery.text.isEmpty) {
setState(() {
_IsSearching = false;
_searchText = "";
});
} else {
setState(() {
_IsSearching = true;
_searchText = _searchQuery.text;
});
}
});
}
#override
void initState() {
super.initState();
_IsSearching = false;
createSearchResultList();
}
void createSearchResultList() {
_list = <SearchResult>[
new SearchResult(name: 'Google'),
new SearchResult(name: 'IOS'),
new SearchResult(name: 'IOS2'),
new SearchResult(name: 'Android'),
new SearchResult(name: 'Dart'),
new SearchResult(name: 'Flutter'),
new SearchResult(name: 'Python'),
new SearchResult(name: 'React'),
new SearchResult(name: 'Xamarin'),
new SearchResult(name: 'Kotlin'),
new SearchResult(name: 'Java'),
new SearchResult(name: 'RxAndroid'),
];
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: key,
appBar: buildBar(context),
body: new Stack(
children: <Widget>[
new Container(
height: 300.0,
padding: EdgeInsets.all(10.0),
child: new Container(
child: ListView(
children: <Widget>[
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
],
),
),
),
displaySearchResults(),
],
));
}
Widget displaySearchResults() {
if (_IsSearching) {
return new Align(
alignment: Alignment.topCenter,
child: searchList());
} else {
return new Align(alignment: Alignment.topCenter, child: new Container());
}
}
ListView searchList() {
List<SearchResult> results = _buildSearchList();
return ListView.builder(
itemCount: _buildSearchList().isEmpty == null ? 0 : results.length,
itemBuilder: (context, int index) {
return Container(
decoration: new BoxDecoration(
color: Colors.grey[100],
border: new Border(
bottom: new BorderSide(
color: Colors.grey,
width: 0.5
)
)
),
child: ListTile(
onTap: (){},
title: Text(results.elementAt(index).name,
style: new TextStyle(fontSize: 18.0)),
),
);
},
);
}
List<SearchResult> _buildList() {
return _list.map((result) => new SearchResult(name: result.name)).toList();
}
List<SearchResult> _buildSearchList() {
if (_searchText.isEmpty) {
return _list.map((result) => new SearchResult(name: result.name)).toList();
} else {
List<SearchResult> _searchList = List();
for (int i = 0; i < _list.length; i++) {
SearchResult result = _list.elementAt(i);
if ((result.name).toLowerCase().contains(_searchText.toLowerCase())) {
_searchList.add(result);
}
}
return _searchList
.map((result) => new SearchResult(name: result.name))
.toList();
}
}
Widget buildBar(BuildContext context) {
return new AppBar(
centerTitle: true,
title: appBarTitle,
actions: <Widget>[
new IconButton(
icon: actionIcon,
onPressed: () {
_displayTextField();
},
),
// new IconButton(icon: new Icon(Icons.more), onPressed: _IsSearching ? _showDialog(context, _buildSearchList()) : _showDialog(context,_buildList()))
],
);
}
String selectedPopupRoute = "My Home";
final List<String> popupRoutes = <String>[
"My Home",
"Favorite Room 1",
"Favorite Room 2"
];
void _displayTextField() {
setState(() {
if (this.actionIcon.icon == Icons.search) {
this.actionIcon = new Icon(
Icons.close,
color: Colors.white,
);
this.appBarTitle = new TextField(
autofocus: true,
controller: _searchQuery,
style: new TextStyle(
color: Colors.white,
),
);
_handleSearchStart();
} else {
_handleSearchEnd();
}
});
}
void _handleSearchStart() {
setState(() {
_IsSearching = true;
});
}
void _handleSearchEnd() {
setState(() {
this.actionIcon = new Icon(
Icons.search,
color: Colors.white,
);
this.appBarTitle = new Text(
"",
style: new TextStyle(color: Colors.white),
);
_IsSearching = false;
_searchQuery.clear();
});
}
}
It is actually very simple. you can refer above answers for details. Let's follow these steps:
Create a list of items we want to have in the autofill menu, lets name it autoList
Create one more emptyList named filteredList
Add all the values of autoList to filterList
void initState() {
filteredList.addAll(autoList);
}
Create a custom search bar widget with a TextField in it
we will be getting a 'value' i.e. the text entered from this Textfield: eg. TextFiled(onchange(value){})
Assuming that we have strings in our autoList, write:
filteredList.removeWhere((i) => i.contains(value.toString())==false);
The complete TextField widget will look like:
TextField(
onChanged: (value) {
setState(() {
filteredList.clear(); //for the next time that we search we want the list to be unfilterted
filteredList.addAll(autoList); //getting list to original state
//removing items that do not contain the entered Text
filteredList.removeWhere((i) => i.contains(value.toString())==false);
//following is just a bool parameter to keep track of lists
searched=!searched;
});
},
controller: editingController,
decoration: InputDecoration(
border: InputBorder.none,
labelText: "Search for the filtered list",
prefixIcon: Icon(Icons.search),
),
),
Now, along the search bar, we just have to display filteredList with ListViewBuilder. done :)
this plugin will helpful for you,loader_search_bar
Flutter widget integrating search field feature into app bar, allowing to receive query change callbacks and automatically load new data set into ListView. It replaces standard AppBar widget and needs to be placed underneath Scaffold element in the widget tree to work properly.
Getting started
To start using SearchBar insert it in place of an AppBar element in the Scaffold widget. Regardless of the use case, defaultBar named argument has to be specified, which basically is a widget that will be displayed whenever SearchBar is not in activated state:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: SearchBar(
defaultBar: AppBar(
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: _openDrawer,
),
title: Text('Default app bar title'),
),
...
),
body: _body,
drawer: _drawer,
);
}
Optional attributes
searchHint - hint string being displayed until user inputs any text,
initialQuery - query value displayed for the first time in search field,
iconified - boolean value indicating way of representing non-activated SearchBar:
true if widget should be showed as an action item in defaultBar,
false if widget should be merged with defaultBar (only leading icon of the default widget and search input field are displayed in such case),
autofocus - boolean value determining if search text field should get focus whenever it becomes visible,
autoActive - ,
attrs - SearchBarAttrs class instance allowing to specify part of exact values used during widget building (e.g. search bar colors, text size, border radius),
controller - SearchBarController object that provides a way of interacing with current state of the widget,
searchItem - defining how to build and position search item widget in app bar,
overlayStyle - status bar overlay brightness applied when widget is activated.
Query callbacks
To get notified about user input specify onQueryChanged and/or onQuerySubmitted callback functions that receive current query string as an argument:
appBar: SearchBar(
...
onQueryChanged: (query) => _handleQueryChanged(context, query),
onQuerySubmitted: (query) => _handleQuerySubmitted(context, query),
),
QuerySetLoader
By passing QuerySetLoader object as an argument one can additionally benefit from search results being automatically built as ListView widget whenever search query changes:
appBar: SearchBar(
...
loader: QuerySetLoader<Item>(
querySetCall: _getItemListForQuery,
itemBuilder: _buildItemWidget,
loadOnEachChange: true,
animateChanges: true,
),
),
List<Item> _getItemListForQuery(String query) { ... }
Widget _buildItemWidget(Item item) { ... }
querySetCall - function transforming search query into list of items being then rendered in ListView (required),
itemBuilder - function creating Widget object for received item, called during ListView building for each element of the results set (required),
loadOnEachChange - boolean value indicating whether querySetCall should be triggered on each query change; if false query set is loaded once user submits query,
animateChanges - determines whether ListView's insert and remove operations should be animated.
SearchItem
Specifying this parameter allows to customize how search item should be built and positioned in app bar. It can be either action or menu widget. No matter which of these two is picked, two constructor arguments can be passed:
builder - function receiving current BuildContext and returning Widget for action or PopupMenuItem for menu item,
gravity - can be one of SearchItemGravity values: start, end or exactly. If no arguments are passed, SearchBar will create default item which is search action icon with start gravity.
SearchItem.action
appBar: SearchBar(
// ...
searchItem: SearchItem.action(
builder: (_) => Padding(
padding: EdgeInsets.all(12.0),
child: Icon(
Icons.find_in_page,
color: Colors.indigoAccent,
),
),
gravity: SearchItemGravity.exactly(1),
),
)
SearchItem.menu
appBar: SearchBar(
// ...
searchItem: SearchItem.menu(
builder: (_) => PopupMenuItem(
child: Text("Search 🔍"),
value: "search",
),
gravity: SearchItemGravity.end,
),
)
Also, bear in mind that SearchBar will prevent built item widget from receiving tap events and will begin search action rather than that.
hope it will help you.
import 'package:flutter/material.dart';
class SearchText extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Searchable Text"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {
showSearch(
context: context,
delegate: DataSearch(),
);
})
],
),
drawer: Drawer(),
);
}
}
class DataSearch extends SearchDelegate<String> {
final cities = ['Ankara', 'İzmir', 'İstanbul', 'Samsun', 'Sakarya'];
var recentCities = ['Ankara'];
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = "";
})
];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () {
close(context, null);
});
}
#override
Widget buildResults(BuildContext context) {
return Center(
child: Container(
width: 100,
height: 100,
child: Card(
color: Colors.red,
child: Center(child: Text(query)),
),
),
);
}
#override
Widget buildSuggestions(BuildContext context) {
final suggestionList = query.isEmpty
? recentCities
: cities.where((p) => p.startsWith(query)).toList();
return ListView.builder(
itemBuilder: (context, index) => ListTile(
onTap: () {
showResults(context);
},
leading: Icon(Icons.location_city),
title: RichText(
text: TextSpan(
text: suggestionList[index].substring(0, query.length),
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
),
children: [
TextSpan(
text: suggestionList[index].substring(query.length),
),
],
),
),
),
itemCount: suggestionList.length,
);
}
}