How to implement search in PaginatedDataTable Flutter Web - flutter

Hi I have a PaginatedDataTable and right now I would like to make it searchable so when I type some keyword it would show the data according to the keyword that users type... I have been searching some articles about it, but I didn't find yet... anyone knows some tutorials or articles or examples about it?

dataList: _searchController.text == ""
? widget.dataList
: searchList,
actions: [
AnimatedSearchBar(
width: 300,
textController: _searchController,
onSuffixTap: () {
setState(() {
_searchController.text = "";
});
}),)]
they are PaginatedDataTable properties
#override
void initState() {
super.initState();
if (searchableItemList.length != 0 && widget.dataList.length != 0) {
_searchController.addListener(() {
setState(() {
searchList = dataList
.where((element) => element
.name
.toLowerCase()
.contains(controller.text.toLowerCase()))
.toList();;
});
});
}}

Related

How to sync stream variable in BlocState with the TextFormField

I am not able to figure out the right architecture of getting this done using firestore streams and flutter_bloc.
My goal :
I have a logged in user and I want to store the user details in the collection with the document id same as logged in user id and want to listen to changes, Throughout the program.
Meanwhile i would want to even store user details (if doesn't exist) and update user details.
My approach:
I am having a usermodel in UserState and i am listening to userModel via state using BlocBuilder.
Problem:
As i am using BlocBuilder my setState isn't working and TextFormField isn't working as it says beginBatchEdit on inactive InputConnection
Code:
UserCubit.dart
class UserCubit extends Cubit<UserState> {
final _firestore = FirebaseFirestore.instance;
final User? _currentUser = FirebaseAuth.instance.currentUser;
UserCubit() : super(UserInitialState()) {
emit(UserMainLoadingState());
_firestore --> listening to stream and updating state
.collection("sample")
.doc(_currentUser?.uid)
.snapshots()
.listen((event) {
event.exists
? emit(UserExists(sample: SampleModel.fromjson(event.data()!, event.id)))
: {emit(UserNotExists())};
});
}
Future<void> addSampleUser({required SampleModel sample}) async {
emit(UserSideLoadingState());
_firestore
.collection('sample')
.doc(_currentUser?.uid)
.set(sample.toJson())
.then((value) => emit(UserSavedUpdatedState()));
}
}
UserState.dart
abstract class UserState extends Equatable {
final SampleModel? sampleModel;
const UserState({this.sampleModel});
#override
List<Object?> get props => [sampleModel];
}
class UserExists extends UserState {
const UserExists({required SampleModel sample}) : super(sampleModel: sample);
}
Widget.dart (Save/Update User Details)
class _MyWidgetState extends State<MyWidget> {
// #override
var fullNameKey;
TextEditingController? fullNameController;
bool _formChecked = false;
TextEditingController? phoneNumberController;
Avatar? selectedAvatar;
#override
void initState() {
fullNameController = TextEditingController();
fullNameKey = GlobalKey<FormState>();
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: BlocConsumer<UserCubit, UserState>(listener: (context, state) {
if (state is UserSavedUpdatedState) {
print("Saved/Updated User");
context.goNamed(Routes.profileMain);
}
}, builder: (context, state) {
// Here i am trying to get details from the state
selectedAvatar = state.sampleModel?.avatar == "boy"
? Avatar.boy
: state.sampleModel?.avatar == "boy"
? Avatar.girl
: null;
fullNameController!.text = state.sampleModel?.name ?? "";
if (state is UserMainLoadingState) {
return const Center(child: CircularProgressIndicator());
}
return Scaffold(
body: Column(
children: [
Row(
children: [
IconButton(
onPressed: () {
setState(() {
selectedAvatar = Avatar.girl; -- > This doesn't work because of BlocBuilder
});
},
icon: const Icon(Icons.girl)),
IconButton(
onPressed: () {
setState(() {
selectedAvatar = Avatar.boy; -- > This doesn't work because of BlocBuilder
});
},
icon: const Icon(Icons.boy))
],
),
Form(
key: fullNameKey,
child: TextFormField(
// BlocBuilder even freezes TextFormField
autovalidateMode: _formChecked
? AutovalidateMode.always
: AutovalidateMode.disabled,
validator: (value) {
if (value == null ||
fullNameController?.text.trim() == "") {
return "Name cannot be empty";
}
if (value.length < 3) {
return "Username must be greater than 3 characters";
}
return null;
},
controller: fullNameController,
decoration: const InputDecoration(
labelText: "Full Name",
),
)).marginDown(),
FilledButton(
onPressed: () {
setState(() {
_formChecked = true;
});
if (fullNameKey.currentState!.validate() &&
selectedAvatar != null) {
SampleModel sample = SampleModel(
name: fullNameController!.text,
avatar: selectedAvatar == Avatar.boy ? "boy" : "girl");
BlocProvider.of<UserCubit>(context)
.addSampleUser(sample: sample);
}
},
child: const Text("Submit"),
)
],
));
}),
);
}
}
As soon as the submit button is clicked it erases the entire text and validator gets activated. Avatar selection doesn't work as well.
What is the best way to achieve the desired function using streams, flutter_bloc, Suggestions would be greatly appreciated
as far as I can see you pre-select the avatar based on the emitted state. However, I do not see that you return the selection via an event/function to the bloc/cubit. So this is needed in order to send the updated avatar with the next emit.
From what I can see, I would also possibly exchange the abstract class state with a class state implementing Equatable and the simply always copyWith the state for any updates. This way you always have the same UserState - no need for if and else if for state selection, however, the data of the state changes based on the situation. I think for a user bloc/cubit this makes the lifecycle a bit easier
UPDATE:
IconButton(
onPressed: () {
setState(() {
context.read<UserCubit>.updateUser(selectedAvatar: Avatar.boy);
selectedAvatar = Avatar.boy; -- > possibly no longer needed if returned from Cubit
});
},
icon: const Icon(Icons.boy))
As for the state management, a state can look like this:
class TaskListState extends Equatable {
const TaskListState({
this.status = DataTransStatus.initial,
this.taskList = const [],
this.filter,
this.error,
this.editThisTaskId,
});
final DataTransStatus status;
final List<TaskListViewmodel> taskList;
final TaskListFilter? filter;
final String? error;
final String? editThisTaskId;
TaskListState copyWith({
DataTransStatus Function()? status,
List<TaskListViewmodel> Function()? taskList,
TaskListFilter Function()? filter,
String Function()? error,
String? Function()? editThisTaskId,
}) {
return TaskListState(
status: status != null ? status() : this.status,
taskList: taskList != null ? taskList() : this.taskList,
filter: filter != null ? filter() : this.filter,
error: error != null ? error() : this.error,
editThisTaskId: editThisTaskId != null
? editThisTaskId() : this.editThisTaskId,
);
}
#override
List<Object?> get props => [
status,
taskList,
filter,
error,
editThisTaskId,
];
}
which you use - in this case with a Stream - like this:
await emit.forEach<dynamic>(
_propertiesRepository.streamTasks(event.propertyId),
onData: (tasks) {
return state.copyWith(
status: () => DataTransStatus.success,
taskList: () => List.from(
tasks.map((t) => TaskListViewmodel.fromDomain(t))),
);
},
onError: (_, __) {
return state.copyWith(
status: () => DataTransStatus.failure,
);
},
);

Not able to update a list with set state in textfield onchanged method

I want to update a list of type SongModel when I enter a value in the TextField. However, the list is not updating when onChanged is called.
List<SongModel> songs = item.data!;
List<SongModel> filterSongs = [];
//showing the songs
return Column(
mainAxisSize: MainAxisSize.max,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
keyboardType: TextInputType.text,
controller: searchController,
onChanged: (value) {
//pass value for the search
getSearch(filterSongs,songs);
},
decoration: InputDecoration(............
getSearch() :
getSearch(List<SongModel> filterSongs,List<SongModel> songs)
{
var text = searchController.text;
if (text.isEmpty) {
setState(() {
filterSongs = songs;
});
}
print(songs.where((SongModel item) => item.title.toLowerCase().contains(text.toLowerCase())).toList());
print(text);
setState(() {
// search = text;
filterSongs = songs.where((SongModel item) => item.title.toLowerCase().contains(text.toLowerCase())).toList();
});
print(filterSongs.length);
}
Here the list is not updating with set state method.
In your getSearch method, you are setting the value of the parameter passed to getSearch, not the value of the list outside of that method. You should move the filterSongs list declaration outside of your build method anyways so that it isn't redeclared every time the screen is rebuilt.
class MyScreenClassState extends State<MyScreenClass>{
//Create State method here
List<SongModel> filterSongs = [];
//Build method here
}
getSearch(List<SongModel> songs)
{
var text = searchController.text;
if (text.isEmpty) {
setState(() {
filterSongs = songs;
});
}
print(songs.where((SongModel item) => item.title.toLowerCase().contains(text.toLowerCase())).toList());
print(text);
setState(() {
// search = text;
filterSongs = songs.where((SongModel item) => item.title.toLowerCase().contains(text.toLowerCase())).toList();
});
print(filterSongs.length);
}
If onChanged method doesn't work for your implementation, you can try this structure I think I will work for you.
final TextEditingController _controller = TextEditingController();
#override
void initState() {
super.initState();
_controller.addListener(_onControllerChange);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
void _onControllerChange() async {
.....
}

Possible to update/rebuild open DropdownButtonFormField?

Is it possible to uppdate or rebuild open DropdownButtonFormField, e.g. with GetIt or Provider - or perhaps by using a stream - when new data becomes available?
At the moment the options in the dropdown gett added as data arrives while the dropdown is un-expanded, but once the user taps on it only those items that were available at the time they tapped are available and new items are only available if and when the user closes and reopens the dropdown.
My user interface currently has
DropdownButtonFormField(
value: dropdownValue,
icon: const Icon(Icons.keyboard_arrow_down),
items: dropdownItems,
validator: (value) =>
(value == null || value == "0") ? 'Please select a recipient.' : null,
onChanged: (String? newValue) {
if (newValue != null) {
dropdownValue = newValue;
} else {
dropdownValue = "0";
}
setState(() {});
},
)
the dropdownItems comes from
List<DropdownMenuItem<String>>? get dropdownItems {
if (clientListicle.items.isEmpty) {
developer.log('go fetch', name: '_MessagePageState get dropdownItems');
fetchClients();
}
for (var element in clientListicle.items) {
DropdownMenuItem<String> potentialItem =
DropdownMenuItem(value: element.id.toString(), child: Text(element.title));
bool isHave = false;
for (var exstElement in menuItems) {
if (exstElement.value == element.id.toString()) {
isHave = true;
}
}
if (!isHave) {
menuItems.add(potentialItem);
setState(() {});
}
}
if (menuItems.length == 1) {
return null;
} else {
return menuItems;
}
}
clientListicle is a singleton that inherits from a class that extends ChangeNotifier, and is registered in GetIt.
I've had a quick look at the implementation of DropdownButtonFormField and FormField that it extends, thinking maybe one could add functionality to or override a build method or some such, but think maybe I'm missing something simpler/easier and am probably just a bit out of my depth here... :-)
Update, I've tried adding a final _formFieldKey = GlobalKey<FormFieldState>(); key to the dropdown widget thinking I might be able to use that from the getter to trigger a rebuild, but no luck yet.

Single Selection for ListView Flutter

I am trying to implement a listView single selection in my app such that once an item in the list is tapped such that pressed item color state is different from the others. I have done all I know but it does not work well. The problem is that even though my implementation updates each item state when pressed, it doesn't reset the others to their initial state.
class BoxSelection{
bool isSelected;
String title;
String options;
BoxSelection({this.title, this.isSelected, this.options});
}
class _AddProjectState extends State<AddProject> {
List<BoxSelection> projectType = new List();
#override
void didChangeDependencies() {
super.didChangeDependencies();
projectType
.add(BoxSelection(title: "Building", isSelected: false, options: "A"));
projectType
.add(BoxSelection(title: "Gym House", isSelected: false, options: "B"));
projectType
.add(BoxSelection(title: "School", isSelected: false, options: "C"));
}
child: ListView.builder(
itemCount: projectType.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
setState(() {
//here am trying to implement single selection for the options in the list but it don't work well
for(int i = 0; i < projectType.length; i++) {
if (i == index) {
setState(() {
projectType[index].isSelected = true;
});
} else {
setState(() {
projectType[index].isSelected = false;
});
}
}
});
},
child: BoxSelectionButton(
isSelected: projectType[index].isSelected,
option: projectType[index].options,
title: projectType[index].title,
),
);
},
),
Your problem is that you're using index to access projectType elements but you should be using i
if (i == index) {
setState(() {
projectType[i].isSelected = true;
});
} else {
setState(() {
projectType[i].isSelected = false;
});
}
In any case I think your code can be improved since it's not as efficient as it could be. You're iterating over the entire list and calling setState twice in every iteration, recreating the widget tree a lot of times unnecessarily when it can be done in one shoot.
Save your current selection in a class level variable
BoxSelection _selectedBox
Simplify your code to act directly over the current selection insted of iterating over the entire list
onTap: () =>
setState(() {
if (_selectedBox != null) {
_selectedBox.isSelected = false;
}
projectType[index].isSelected = !projectType[index].isSelected;
_selectedBox = projectType[index];
});

Data is showing only after I hot reload or refresh in flutter

In my database I have a user table where username, some marks are stored. I want to view that data in a table when user go to a particular page. So, when I go to that page at first the data(username and marks) doesn't show. But if I hot reload or use a button to refresh then the data shows properly. My question is how can I do that without hot reload or using a refresh button. Here is my code:
import 'package:flutter/material.dart';
import 'package:flutter_app/database/db_helper.dart';
import 'package:flutter_app/database/user.dart';
class ViewResult extends StatefulWidget {
#override
_ViewResult createState() => _ViewResult();
}
GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey();
class _ViewResult extends State<ViewResult> {
String tempEmail;
bool check = true;
var dbHelper;
var data = List<User>();
#override
void initState() {
setState(() {
dbHelper = DBHelper();
resultData();
});
super.initState();
}
void resultData() {
dbHelper.getUser().then((users) {
for (User temp in users) {
if (temp.type == "student") data.add(temp);
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text("Result"),
),
body: DataTable(
columnSpacing: 22,
columns: [
DataColumn(label: Text("Username")),
DataColumn(label: Text("Beginner")),
DataColumn(label: Text("Intermediate")),
DataColumn(label: Text("Advanced")),
],
rows: data
.map((user) => DataRow(cells: [
DataCell(Text(user.email)),
DataCell(Text(user.beginnerMark.toString())),
DataCell(Text(user.intermediateMark.toString())),
DataCell(Text(user.advancedMark.toString())),
]))
.toList(),
),
backgroundColor: Color.fromRGBO(130, 178, 220, 1),
);
}
}
TLDR: Call setState() from inside then() if not, it will be executed before everything completes and have no effect at all.
getUser().then((response) {
//Your stuff
setState(() {}); //refresh
});
You have to use setState after you have loaded the data and your resultData functions is working with then, that is executed after the initial setState in the initState is finished.
#override
void initState() {
// super.initState(); should be at the start of the method
super.initState();
dbHelper = DBHelper();
resultData();
}
void resultData() {
dbHelper.getUser().then((users) {
for (User temp in users) {
if (temp.type == "student") data.add(temp);
}
// since you have already added the results to data
// setState can have an empty function body
setState(() {});
});
}
I prefer working with async/await instead of then so my solutions would be as follows:
Future<void> resultData() async {
List<User> users = await dbHelper.getUser();
setState(() {
data = users.where((user) => user.type == "student");
});
}
You need setState(() {});.
Add it near the end of resultData function:
void resultData() {
dbHelper.getUser().then((users) {
for (User temp in users) {
if (temp.type == "student") data.add(temp);
}
setState(() {});
});
}
See setState for more information.