Flutter - Autocomplete with displaying selected item information on screen - flutter

I'm trying to implement Autocomplete text, then display records related to selected item. For example, If I select 'IBM' from below example, display records related to IBM in a listview.
Autocomplete is working as expected but upon selecting the item list view is not generating. i.e. in onSelected(), widget buildPositions() should be corrected, any help would be highly appreciated.
import 'package:e2/Models/model_positions.dart';
import 'package:flutter/material.dart';
class ContractControl extends StatefulWidget {
const ContractControl({super.key});
#override
State<ContractControl> createState() => _ContractControlState();
}
class _ContractControlState extends State<ContractControl> {
List<dynamic> _selectedItems = [];
static const List<String> listItems = <String>['TCS', 'IBM', 'WIPRO'];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Contract Control"),
centerTitle: true,
//automaticallyImplyLeading: false,
),
body: Autocomplete(optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<String>.empty();
}
return listItems.where((String item) {
return item.contains(textEditingValue.text.toUpperCase());
});
}, onSelected: (String item) {
buildPositions(item);
}));
}
## *****************need correction here ******************
Widget buildPositions(String item) {
return Container(
child: FutureBuilder<List<dynamic>>(
future: ModelsPositions().detailedContractControlData(item),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<dynamic> positions = snapshot.data ?? [];
return ListView.builder(
itemCount: positions.length,
itemBuilder: (context, index) {
return Card(
child: Row(children: [
Checkbox(
value: _selectedItems.contains(positions[index]),
onChanged: (value) {
setState(() {
if (value == null) {
return null;
}
if (value) {
_selectedItems.add(positions[index]);
} else {
_selectedItems
.removeWhere((item) => item == positions[index]);
}
});
},
),
]));
},
);
} else if (snapshot.hasError) {
return Center(
child: Text('Failed to fetch Positions Summary'),
);
}
return Center(
child: CircularProgressIndicator(),
);
},
),
);
}
}

Related

Flutter : scrollController.isAttached is always false

How can I scroll to a special widget in a ListView? For example, I want to automatically scroll to some container in ListView if I press a certain button on a previous screen. I will pass to the next screen an Id (from id I will know the index) and when I navigate to the next screen I want to automatically scroll to this widget.
the code in main screen : Navigator.push(context, MaterialPageRoute(builder: (_) => CreatedEstatesScreen(estateId: id)));
the code in the next screen :
class RecentEstateOrdersScreen extends StatefulWidget {
static const String id = "RecentEstateOrdersScreen";
String? estateId;
RecentEstateOrdersScreen({Key? key, this.estateId}) : super(key: key);
#override
_RecentEstateOrdersScreenState createState() =>
_RecentEstateOrdersScreenState();
}
class _RecentEstateOrdersScreenState extends State<RecentEstateOrdersScreen> {
late RecentEstatesOrdersBloc _recentEstatesOrdersBloc;
late ItemScrollController scrollController;
late ItemPositionsListener itemPositionsListener;
String? userToken;
List<EstateOrder> orders = [];
#override
void initState() {
super.initState();
_recentEstatesOrdersBloc = RecentEstatesOrdersBloc(EstateOrderRepository());
_onRefresh();
User? user = BlocProvider.of<UserLoginBloc>(context).user;
if (user != null && user.token != null) {
userToken = user.token;
}
scrollController = ItemScrollController();
itemPositionsListener = ItemPositionsListener.create();
}
_onRefresh() {
if (BlocProvider.of<UserLoginBloc>(context).user!.token != null) {
_recentEstatesOrdersBloc.add(
RecentEstatesOrdersFetchStarted(
token: BlocProvider.of<UserLoginBloc>(context).user!.token!),
);
}
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text(
AppLocalizations.of(context)!.recent_created_orders,
),
),
body: BlocConsumer<RecentEstatesOrdersBloc, RecentEstatesOrdersState>(
bloc: _recentEstatesOrdersBloc,
listener: (context, recentOrdersState) async {
if (recentOrdersState is RecentEstatesOrdersFetchError) {
var error = recentOrdersState.isConnectionError
? AppLocalizations.of(context)!.no_internet_connection
: recentOrdersState.error;
await showWonderfulAlertDialog(
context, AppLocalizations.of(context)!.error, error);
}
},
builder: (BuildContext context, recentOrdersState) {
if (recentOrdersState is RecentEstatesOrdersFetchProgress) {
return const ClientsOrdersShimmer();
}
if (recentOrdersState is! RecentEstatesOrdersFetchComplete) {
return Container();
}
orders = recentOrdersState.estateOrders;
if (orders.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
documentOutlineIconPath,
width: 0.5.sw,
height: 0.5.sw,
color: Theme.of(context)
.colorScheme
.onBackground
.withOpacity(0.42),
),
48.verticalSpace,
Text(
AppLocalizations.of(context)!.have_not_recent_orders,
style: Theme.of(context).textTheme.headline4,
),
],
),
);
}
if (widget.estateId != null) {
SchedulerBinding.instance!.addPostFrameCallback((_) {
jumpToOrder(orders);
});
}
return RefreshIndicator(
color: Theme.of(context).colorScheme.primary,
onRefresh: () async {
_onRefresh();
},
child: ListView.builder(
itemCount: orders.length,
itemBuilder: (_, index) {
return EstateOrderCard(
estateOrder: orders.elementAt(index),
);
}),
);
},
),
),
);
}
jumpToOrder(List<EstateOrder> orders) {
int index = getIndexFromId(orders);
if (index != -1) {
if (scrollController.isAttached) {
scrollController.scrollTo(
index: index,
duration: const Duration(seconds: 2),
curve: Curves.easeInOutCubic);
}
}
}
getIndexFromId(List<EstateOrder> orders) {
for (int i = 0; i < orders.length; i++) {
if (orders.elementAt(i).id == int.parse(widget.estateId!)) {
return i;
}
}
return -1;
}
}```
If you are using the library then you have to use ScrollablePositionedList.builder, not the normal ListView.builder.

When I switch tabs, the content (StreamBuilder) of the previous tab is lost, leaving a blank page

I have two tabs, in each one the content is a StreamBuilder.
The StreamBuilder returns a column with a text field to search on a ListView and another StreamBuilder, the latter returns a ListView. When I move to another tab and subsequently return to the previous one, leaving a blank page.
When I move to another tab and subsequently return to the previous one, the content is lost, leaving a blank page. How can you avoid this?
Here is the relevant code:
class ListaCredencialesCapturistaPantalla extends StatefulWidget {
static const String id = "ListaCredencialesCapturistaPantalla";
const ListaCredencialesCapturistaPantalla({Key? key}) : super(key: key);
#override
_ListaCredencialesCapturistaPantallaState createState() => _ListaCredencialesCapturistaPantallaState();
}
class _ListaCredencialesCapturistaPantallaState
extends State<ListaCredencialesCapturistaPantalla>
with SingleTickerProviderStateMixin {
final List<CredencialModelo> _listaCredencialesActivos = <CredencialModelo>[];
final List<CredencialModelo> _listaCredencialesFinados = <CredencialModelo>[];
final StreamController<List<CredencialModelo>> _controladorStreamActivos = StreamController<List<CredencialModelo>>();
Stream<List<CredencialModelo>> get _streamActivos => _controladorStreamActivos.stream;
final StreamController<List<CredencialModelo>> _controladorStreamFinados = StreamController<List<CredencialModelo>>();
Stream<List<CredencialModelo>> get _streamFinados => _controladorStreamFinados.stream;
bool get wantKeepAlive => true;
#override
void initState() {
super.initState();
}
#override
void dispose() {
_controladorStreamActivos.close();
_controladorStreamFinados.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return _construyeInterfaz();
}
Widget _construyeInterfaz() {
return DefaultTabController(
length: 2,
child: Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
title: const Text('Listado de Credenciales'),
bottom: const TabBar(
tabs: <Widget>[
Tab(
icon: Constantes.ICONO_USUARIO_ACTIVO,
text: 'Activo',
),
Tab(
icon: Constantes.ICONO_USUARIO_FINADO,
text: 'Finado',
),
],
),
),
body: TabBarView(
children: <Widget>[
_construyeSeccionBusquedaListado(EstatusUsuario.activo),
_construyeSeccionBusquedaListado(EstatusUsuario.finado),
],
),
floatingActionButton: FloatingActionButton(
child: Constantes.ICONO_AGREGAR,
onPressed: () {
Navigator.pushNamed(context, AgregaCredencialPantalla.id);
},
),
),
);
}
Widget _construyeSeccionBusquedaListado(EstatusUsuario estatusUsuario) {
return StreamBuilder(
stream: Firestore.listaCredenciales(estatusUsuario),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.data!.docs.isEmpty) {
return Center(
child: Column(
children: const <Widget>[
Icon(FontAwesomeIcons.exclamationCircle),
Text('Sin credenciales'),
],
),
);
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(color: Constantes.COLOR_INTERFAZ,),
);
}
List<CredencialModelo> listaCredenciales = <CredencialModelo>[];
for (QueryDocumentSnapshot<Map<String, dynamic>> elemento in snapshot.data!.docs) {
listaCredenciales.add(CredencialModelo.fromMapCredencial(elemento.data()));
}
if (estatusUsuario == EstatusUsuario.activo) {
_listaCredencialesActivos.clear();
_listaCredencialesActivos.addAll(listaCredenciales);
}
else {
_listaCredencialesFinados.clear();
_listaCredencialesFinados.addAll(listaCredenciales);
}
return _construyeBusquedaListado(estatusUsuario);
}
);
}
Widget _construyeBusquedaListado(EstatusUsuario estatusUsuario) {
return Column(
children: [
_construyeCampoBusqueda(estatusUsuario),
_construyeListaFiltrable(
estatusUsuario,
estatusUsuario == EstatusUsuario.activo ?
_listaCredencialesActivos :
_listaCredencialesFinados
),
],
);
}
Widget _construyeCampoBusqueda(EstatusUsuario estatusUsuario) {
return RoundedIconTextFormField(
textCapitalization: TextCapitalization.characters,
inputFormatters: [FormateoTextoMayusculasInput()],
labelText: 'Búsqueda',
prefixIcon: Constantes.DATO_ICONO_BUSQUEDA,
onChanged: (String busqueda) {
_busquedaCredencial(busqueda.toUpperCase(), estatusUsuario);
},
validator: (String? busqueda) {},
);
}
void _busquedaCredencial(String busqueda, EstatusUsuario estatusUsuario) {
if (busqueda.isNotEmpty) {
List<CredencialModelo> listaResultados =
(estatusUsuario == EstatusUsuario.activo ?
_listaCredencialesActivos :
_listaCredencialesFinados
).where(
(elemento) {
return elemento.curp!.contains(busqueda) || elemento.apePat!.contains(busqueda) ||
elemento.apeMat!.contains(busqueda) || elemento.nombre!.contains(busqueda) ||
elemento.municipio!.toString().contains(busqueda);
}
).toList();
(estatusUsuario == EstatusUsuario.activo ?
_controladorStreamActivos :
_controladorStreamFinados).sink.add(listaResultados);
}
else {
(estatusUsuario == EstatusUsuario.activo ?
_controladorStreamActivos :
_controladorStreamFinados
)
.sink
.add(estatusUsuario == EstatusUsuario.activo ?
_listaCredencialesActivos :
_listaCredencialesFinados
);
}
}
Widget _construyeListaFiltrable(EstatusUsuario estatusUsuario, List<CredencialModelo> listaCredenciales) {
return StreamBuilder<List<CredencialModelo>>(
key: ValueKey(listaCredenciales),
initialData: listaCredenciales,
stream: estatusUsuario == EstatusUsuario.activo ? _streamActivos : _streamFinados,
builder: (BuildContext context, AsyncSnapshot<List<CredencialModelo>> snapshot) {
if (snapshot.data!.isEmpty) {
return Center(
child: Column(
children: const <Widget>[
Icon(FontAwesomeIcons.exclamationCircle),
Text('Sin resultados'),
],
),
);
}
return _construyeListaCredenciales(snapshot.data!);
},
);
}
Widget _construyeListaCredenciales(List<CredencialModelo> listaCredenciales) {
return Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: listaCredenciales.length,
itemBuilder: (context, position) {
return _construyeCredencial(listaCredenciales[position]);
}
),
);
}
}
You can do it this way...
Create a StatefulWidget (it's technically a new page) instead of a simple Widget.
Something Like that:
class ConstruyeSeccionBusquedaListado extends StatefulWidget {
final EstatusUsuario estatusUsuario;
const ConstruyeSeccionBusquedaListado({Key? key, required this.estatusUsuario}) : super(key: key);
#override
State<ConstruyeSeccionBusquedaListado> createState() => _construyeSeccionBusquedaListado();
}
class _construyeSeccionBusquedaListado extends State<ConstruyeSeccionBusquedaListado> with AutomaticKeepAliveClientMixin<ConstruyeSeccionBusquedaListado> {
#override
Widget build(BuildContext context) {
return StreamBuilder<Object>(
stream: null,
builder: (context, snapshot) {
return Container();
}
);
}
#override
bool get wantKeepAlive => true;
}
And on your TabBarView do the following:
TabBarView(
children: [
ConstruyeSeccionBusquedaListado(estatusUsuario: EstatusUsuario.activo),
],
)
Don't forget to put the:
with AutomaticKeepAliveClientMixin<>
and the:
#override
bool get wantKeepAlive => true;
on your StatefulWidget.
This worked for me.
Source: https://github.com/flutter/flutter/issues/19116

How to create Dynamic Tabs, equals to the length of list

How can I create Tabs equals to the length of a list and their name should be equals to the name of items in list and I'm fetching the list from firebase realtime database.
Here's my code:
class _ItemDetailsDemoState extends State<ItemDetailsDemo> with SingleTickerProviderStateMixin {
#override
void initState() {
getSubCategories();
super.initState();
}
List<SubCategoryLoader> subCategoryLoaderList = List();
Future<void> getSubCategories() async {
await FirebaseDatabase.instance.reference().child("SubCategoryNames").once()
.then((DataSnapshot snapshot) {
var key = snapshot.value.keys;
;
for (var i in key) {
SubCategoryLoader subCategoryLoader = new SubCategoryLoader(
snapshot.value[i]['Name']
);
subCategoryLoaderList.add(subCategoryLoader);
}
for (int j = 0; j < subCategoryLoaderList.length; j++) {
print(subCategoryLoaderList[j].Name);
}
if (mounted) {
setState(() {
print(subCategoryLoaderList.length);
});
}
});
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: FirebaseDatabase.instance.reference().child("SubCategoryNames").once(),
builder: (context,snapshot){
if(snapshot.hasData){
if(snapshot.data!=null)
{
return DefaultTabController(
length: subCategoryLoaderList.length, // I made this dynamic but this is throwing an error "controller's length property does not match with number of tabs, this is because my Tab is static which is 2 how can I make it dynamic.
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.looks_one), text: "List1"), //How can i make this dynamic and text:"List1" must be the name of list items
Tab(icon: Icon(Icons.looks_two), text: "List2"),
],
),
),
body: TabBarView(
children: [
_buildList(key: "key1", string: "a"),
_buildList(key: "key2", string: "List2: "),
],
),
));
}else{
return Loader();
}
}else{
return Loader();
}
},
);
}
Widget _buildList({String key, String string}) {
return ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: subCategoryLoaderList.length,
itemBuilder: (context, index1){
return Container(
child: Text(subCategoryLoaderList[index1].Name+string),
);
},
);
}
}
And I also want TabBarView to be dynamic as well, so that it will render the items accordingly.I need to fetch all the data belongs to that subcategory.
The number of tabs and TabBarView's children must be the same as DefaultTabController's length. one way of doing that is to have a map function that turns SubCategoryLoader into Tabs or pages:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: FirebaseDatabase.instance
.reference()
.child("SubCategoryNames")
.once(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data != null) {
return DefaultTabController(
length: subCategoryLoaderList.length,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: subCategoryLoaderList
.map((subCatagory) => Tab(text: subCatagory.Name))
.toList(),
),
),
body: TabBarView(
children: subCategoryLoaderList.map((sub){
return _buildList(key: "key${sub.id}", string: "some string");
}).toList(),
),
));
} else {
return Loader();
}
} else {
return Loader();
}
},
);
}

Flutter FutureBuilder expecting return value

I have below code and it's giving me a warning as below and during runtime it says A build function returned null.
This function has a return type of 'Widget', but doesn't end with a return statement.
Try adding a return statement, or changing the return type to 'void'.
UPDATE:
What's wrong in below code .?.
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart';
import 'package:permission_handler/permission_handler.dart';
List<FileSystemEntity> _pdf = [];
class BrowserScaffold extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return TabsApp();
}
}
Future<List> loadfiles() async {
_pdf = [];
int filecount = 0;
var status = await Permission.storage.status;
if (status.isUndetermined) {
await [
Permission.storage,
].request();
}
Directory dir = Directory('/storage/emulated/0/');
String pdfpath = dir.toString();
print('PATH IS ' + pdfpath);
List<FileSystemEntity> _files;
_files = dir.listSync(recursive: true, followLinks: false);
for (FileSystemEntity entity in _files) {
String path = entity.path;
if (path.endsWith('.pdf')) _pdf.add(entity);
}
for (var i = 0; i < _pdf.length; i++) {
//print(_pdf[i]);
}
filecount = _pdf.length;
print('#############ENTERED');
print(filecount);
return _pdf;
}
class TabsApp extends State<BrowserScaffold> {
#override
Widget build(BuildContext context) {
return Container(
child: DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('MY Files'),
bottom: TabBar(tabs: [
Tab(text: 'ALL FILES'),
Tab(text: 'RECENT FILES'),
]),
),
body: TabBarView(
children: [
RaisedButton(
child: Text('LIST FILES'),
onPressed: () => loadfiles(),
),
FutureBuilder(
future: loadfiles(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
print('OKOK##################################');
if (snapshot.data != null) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Container(
child: Card(
child: Text(
basename(snapshot.data[index].path),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18),
),
));
//return Text(snapshot.data[index].path);
});
} else {
print('FAIL##################################');
return new CircularProgressIndicator();
}
} else {
print('FAIL2##################################');
return Text("Empty");
}
}),
],
),
),
),
);
}
}
This function has a return type of 'Widget', but doesn't end with a
return statement. Try adding a return statement, or changing the
return type to 'void'.
The warning told you everything. You have two if there, so you need to have two else too.
if (snapshot.hasData) {
if (snapshot.data != null) {
...
}else{
return Text("It is null");
}
}else{
return Text("Empty");
}

Flutter: how to filter listview with the data loaded from API

I have a list populated with a Future builder. The items are loaded correctly in the list from API.
Following is the relevant part of the code. I have a textfield in an appbar, which I want to use to filter the list.
List newList = List();
List originalList = List();
bool _showSearchBox = false;
TextEditingController _textController = TextEditingController();
Future _future;
#override
void initState() {
_future = commonApiProvider.fetchUserList(offset, widget.selectedDate);
super.initState();
}
#override
Widget build(BuildContext context) {
size = Screen(MediaQuery.of(context).size);
loadMoreNewStatus = ItemLoadMoreStatus.LOADING;
return Scaffold(
backgroundColor: Color(0xfff0f0f0),
appBar: AppBar(
automaticallyImplyLeading: _showSearchBox == true ? false : true,
backgroundColor: CustomColors.absentTileColor,
elevation: 1,
title:
_showSearchBox == true ? _buildSearchWidget() : Text("Absent List"),
actions: <Widget>[
_showSearchBox == false ? _buildSearchIcon() : Container(),
],
),
body: FutureBuilder(
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.none &&
snapshot.hasData == null) {
return Text("Records not found for selected date.");
} else if (snapshot.hasData) {
return _buildListChild(snapshot);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
},
future: _future,
),
);
}
Widget _buildListChild(AsyncSnapshot snapshot) {
var data = snapshot.data.d;
newList = json.decode(data.userList);
originalList = json.decode(data.userList);
return RefreshIndicator(
key: _refreshIndicatorKey,
child: NotificationListener(
onNotification: onNotificationHandler,
child: ListView.builder(
padding: EdgeInsets.only(top: size.getSizePx(10)),
scrollDirection: Axis.vertical,
shrinkWrap: true,
physics: const BouncingScrollPhysics(),
itemCount: newList.length,
controller: scrollContainer,
itemBuilder: (context, index) {
if (index == newList.length) {
return _buildProgressIndicator();
} else {
loadMoreNewStatus = ItemLoadMoreStatus.STABLE;
animationController.forward();
return cardView(newList[index]);
}
}),
),
onRefresh: _refreshStuffs,
);
}
Widget cardView(userList){
//build list items here.
}
bool onNotificationHandler(ScrollNotification notification){
//stuffs here
}
_refreshStuffs(){
//code to refresh list.
}
Widget _buildSearchWidget(){
return Container(
child: TextField(
controller: _textController,
style: TextStyle(fontSize: 14.0, color: Colors.grey[800]),
onChanged: onSearchTextChanged,
);
);
}
onSearchTextChanged(String text) async {
List tempSearchList = List();
tempSearchList.addAll(originalList);
if (text.isNotEmpty) {
List tempListData = List();
tempSearchList.forEach((item) {
String empName = item["empname"];
if (empName.toLowerCase().contains(text.toLowerCase())) {
tempListData.add(item);
}
});
setState(() {
newList.clear();
newList.addAll(tempListData);
});
return;
} else {
setState(() {
newList.clear();
newList.addAll(originalList);
});
}
}
Problem
The problem is that above code is not working, the list doesn't change at all. If I debug method onSearchTextChanged it works very well. I have cleared newList on this method as well, but doesn't seem to work. Can anybody help how to achieve filter?
The idea here is: Once FutureBuilder completes, it doesn't get rebuild.
I hope the code below helps. Let me know if your problem exists.
class _MyHomePageState extends State<MyHomePage> {
var items = [];
#override
void initState() {
callApi();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: YourFilteringTextField(),
),
body: ListView.builder(
itemBuilder: (context, position) {
return Text(items[position]);
},
itemCount: items.length,
),
);
}
callApi() {
//call api to get your latest items
setState(() {
// items= itemsFetchedFromApi;
});
}
filter(query) {
//applyFilter
setState(() {
// items= itemsAfterFiltering;
});
}
}