Flutter: Help me search Algolia data on the app - flutter

I am working on a real estate app where I would like to display a list of properties, that is retrieved from the Algolia database, and search them from the search input field by typing the I.D of the properties. Like this
I have successfully linked/setup Firebase & Algolia. And I am able to display the properties on the screen, using infinite_scroll_pagination and algolia_helper_flutter packages.
The problem I am facing is I cannot search the houses by typing the I.D of the properties.
Please check out my code, tell me where I went wrong. Thank you.
Best,
class HousesListView extends StatefulWidget {
const HousesListView({Key? key}) : super(key: key);
#override
State<HousesListView> createState() => _HousesListViewState();
}
class _HousesListViewState extends State<HousesListView> {
// textController for search box input
final _searchTextController = TextEditingController();
// pageController from infinite_scroll_pagination package
final PagingController<int, MdlAlgoliaProperties> pagingController =
PagingController(firstPageKey: 0);
/// Component holding search filters from algolia_helper_flutter package
final _filterState = FilterState();
// search houses in Algolia Database
final _houseDatabase = HitsSearcher.create(
applicationID: AlgoliaCredentials.applicationID,
apiKey: AlgoliaCredentials.apiKey,
state: const SearchState(
indexName: AlgoliaCredentials.hitsIndex,
facetFilters: ['a2-propertyType: House']));
// stream and display list of properties on the screen
Stream<PropertiesPage> get displayPropertiesOnThePage =>
_houseDatabase.responses.map(PropertiesPage.fromResponse);
/// Get stream of search result, like the number of the result from the search box
Stream<SearchMetadata> get searchMetadata =>
_houseDatabase.responses.map(SearchMetadata.fromResponse);
#override
void initState() {
super.initState();
// listen to keystroke & query the results by the letters that user types in
_searchTextController
.addListener(() => _houseDatabase.query(_searchTextController.text));
// load properties on the page
displayPropertiesOnThePage.listen((properties) {
if (properties.pageKey == 0) pagingController.refresh();
pagingController.appendPage(
properties.alogliaPPT, properties.nextPageKey);
}).onError((error) => pagingController.error = error);
// error here!
// this loads the list of house successfully and properly when its enabled, but search does not work anymore
// but, when this disable, the search works, but it does not load the list of houses anymore
pagingController.addPageRequestListener((pageKey) =>
_houseDatabase.applyState((state) => state.copyWith(page: pageKey))); //<= error occur in this line
// connect database and filter state
_houseDatabase.connectFilterState(_filterState);
// pageController listens to filterState
_filterState.filters.listen((_) => pagingController.refresh());
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: appBarTitle(context, 'List of Houses'),
backgroundColor: ZayyanColorTheme.zayyanGrey,
endDrawer: const Drawer(
width: 350,
child: HouseFilter(),
),
body: Center(
child: Column(
children: [
SizedBox(
height: 44,
child: TextField(
controller: _searchTextController,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: 'Enter a search term',
prefixIcon: Icon(Icons.search),
),
),
),
StreamBuilder<SearchMetadata>(
stream: searchMetadata,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text('${snapshot.data!.nbHits} hits'),
);
},
),
Expanded(
child: _hits(context),
),
],
),
),
);
}
Widget _hits(BuildContext context) {
return PropertyHitsListView(
pagingController: pagingController,
noItemsFound: (context) => const NoResultsView(),
onHitClick: (objectID) {
print(objectID);
},
);
}
#override
void dispose() {
_searchTextController.dispose();
_houseDatabase.dispose();
_filterState.dispose();
pagingController.dispose();
super.dispose();
}
}

Related

How to dynamically add a TextFormField with initialization data in the middle of a list of UI TextFormFields?

I have a list of dynamic forms where I need to add and remove form fields between two fields dynamically. I am able to add/remove form fields from the bottom of the list properly.
However, when I try to add a form field in between two form fields the data for the field does not update correctly.
How can I correctly add a field in between the two fields and populate the data correctly?
import 'package:flutter/material.dart';
class DynamicFormWidget extends StatefulWidget {
const DynamicFormWidget({Key? key}) : super(key: key);
#override
State<DynamicFormWidget> createState() => _DynamicFormWidgetState();
}
class _DynamicFormWidgetState extends State<DynamicFormWidget> {
List<String?> names = [null];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dynamic Forms'),
),
body: ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
itemBuilder: (builderContext, index) => Row(
children: [
Flexible(
child: TextFormField(
initialValue: names[index],
onChanged: (name) {
names[index] = name;
debugPrint(names.toString());
},
decoration: InputDecoration(
hintText: 'Enter your name',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8))),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: IconButton(
onPressed: () {
setState(() {
if(index + 1 == names.length){
names.add( null); debugPrint('Added: $names');
} else {
names.insert(index + 1, null); debugPrint('Added [${index+1}]: $names');
}
});
},
color: Colors.green,
iconSize: 32,
icon: const Icon(Icons.add_circle)),
),
Padding(
padding: const EdgeInsets.all(8),
child: IconButton(
onPressed: (index == 0&& names.length == 1)
? null
: () {
setState(() {
names.removeAt(index);
});
debugPrint('Removed [$index]: $names');
},
color: Colors.red,
iconSize: 32,
icon: const Icon(Icons.remove_circle)),
),
],
),
separatorBuilder: (separatorContext, index) => const SizedBox(
height: 16,
),
itemCount: names.length,
),
);
}
}
Basically the problem is that Flutter is confused about who is who in your TextFormField list.
To fix this issue simply add a key to your TextFormField, so that it can be uniquely identified by Flutter:
...
child: TextFormField(
initialValue: names[index],
key: UniqueKey(), // add this line
onChanged: (name) {
...
If you want to learn more about keys and its correct use take a look at this.
The widget AnimatedList solves this problem, it keep track of the widgets as a list would do and uses a build function so it is really easy to sync elements with another list. If you end up having a wide range of forms you can make use of the InheritedWidget to simplify the code.
In this sample i'm making use of the TextEditingController to abstract from the form code part and to initialize with value (the widget inherits from the ChangeNotifier so changing the value will update the text in the form widget), for simplicity it only adds (with the generic text) and removes at an index.
To make every CustomLineForm react the others (as in: disable remove if it only remains one) use a StreamBuilder or a ListModel to notify changes and make each entry evaluate if needs to update instead of rebuilding everything.
class App extends StatelessWidget {
final print_all = ChangeNotifier();
App({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: FormList(print_notifier: print_all),
floatingActionButton: IconButton(
onPressed: print_all.notifyListeners,
icon: Icon(Icons.checklist),
),
),
);
}
}
class FormList extends StatefulWidget {
final ChangeNotifier print_notifier;
FormList({required this.print_notifier, super.key});
#override
_FormList createState() => _FormList();
}
class _FormList extends State<FormList> {
final _controllers = <TextEditingController>[];
final _list_key = GlobalKey<AnimatedListState>();
void print_all() {
for (var controller in _controllers) print(controller.text);
}
#override
void initState() {
super.initState();
widget.print_notifier.addListener(print_all);
_controllers.add(TextEditingController(text: 'Inital entrie'));
}
#override
void dispose() {
widget.print_notifier.removeListener(print_all);
for (var controller in _controllers) controller.dispose();
super.dispose();
}
void _insert(int index) {
final int at = index.clamp(0, _controllers.length - 1);
_controllers.insert(at, TextEditingController(text: 'Insert at $at'));
// AnimatedList will take what is placed in [at] so the controller
// needs to exist before adding the widget
_list_key.currentState!.insertItem(at);
}
void _remove(int index) {
final int at = index.clamp(0, _controllers.length - 1);
// The widget is replacing the original, it is used to animate the
// disposal of the widget, ex: size.y -= delta * amount
_list_key.currentState!.removeItem(at, (_, __) => Container());
_controllers[at].dispose();
_controllers.removeAt(at);
}
#override
Widget build(BuildContext context) {
return AnimatedList(
key: _list_key,
initialItemCount: _controllers.length,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
itemBuilder: (ctx, index, _) {
return CustomLineForm(
index: index,
controler: _controllers[index],
on_insert: _insert,
on_remove: _remove,
);
},
);
}
}
class CustomLineForm extends StatelessWidget {
final int index;
final void Function(int) on_insert;
final void Function(int) on_remove;
final TextEditingController controler;
const CustomLineForm({
super.key,
required this.index,
required this.controler,
required this.on_insert,
required this.on_remove,
});
#override
Widget build(BuildContext context) {
return Row(
children: [
Flexible(
child: TextFormField(
controller: controler,
),
),
IconButton(
icon: Icon(Icons.add_circle),
onPressed: () => on_insert(index),
),
IconButton(
icon: Icon(Icons.remove_circle),
onPressed: () => on_remove(index),
)
],
);
}
}

Flutter keyboard stays open after changing screen

I have several TextFormField on a screen. If I tap one of the fields the keyboard opens as expected however if I then select a new screen from the Drawer menu the keyboard closes and as soon as the new screen finishes loading the keyboard automatically opens again. More than that if I type something the text field is updated in the background if I return to the screen with the TextFormField it shows the correct input.
I would expect the screen/widget to be disposed of when navigating to another screen(widget) from the navigation menu, and I definitely should not be able to update the content of a widget's text field while in another widget.
// Form Field
Form(key: _constructionFormKey,
child: Column(children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(
width: 100,
child: Text(
'Homes',
style: regularBoldText,
),
),
SizedBox(
width: 75,
child: Text(
'${widget.tribe.homes} (${calculatePercent(widget.tribe.land, widget.tribe.homes)}%)',
style: regularText,
),
),
SizedBox(
height: 18,
width: MediaQuery.of(context).size.width / 3,
child: TextFormField(
autovalidateMode:
AutovalidateMode.onUserInteraction,
onChanged: (String? newValue) {
if (newValue != null && isNumber(newValue)) {
setState(() {
buildHomes = int.parse(newValue);
});
// Requiered or variable will not clear properly
// when the user deletes input content
} else if (newValue == null || newValue.isEmpty) {
setState(() {
buildHomes = 0;
});
}
},
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
style: const TextStyle(fontSize: 10),
decoration: const InputDecoration(
border: OutlineInputBorder()),
keyboardType: TextInputType.number,
),
),
],
),));
// Home Screen where I have the navigation logic.
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
//! Default to tribe overview screen - 1 -, 0 is mail screen
int _drawerNavIndex = 3;
/// [setSelectedTab] will update the current screen based on the tapped option
/// from [DrawerContentWidget]
void setSelectedTab(index) {
// if the [_drawerNavIndex] is not the same as [index] update it to [index]
// value
if (_drawerNavIndex != index) {
setState(() {
_drawerNavIndex = index;
});
}
}
/// [selectedTabContent] will return the screen selected from the
/// [DrawerContentWidget] based on [_drawerNavIndex]
Widget selectedTabContent() {
List<Widget> pages = [
// Tribe Screens
const TribeMailScreen(),
const TribeHomeScreen(),
const TribeAdvisorScreen(),
const ConstructionScreen()
// Alliance
];
return IndexedStack(
index: _drawerNavIndex,
children: pages,
);
}
#override
Widget build(BuildContext context) {
TribeSummary tribe = Provider.of<TribeSummary>(context, listen: true);
// If the tribe uid value is `placeHolderTribe` assume that there is no
// existing or active tribe for this account
if (tribe.uid == 'placeHolderTribe') {
return Scaffold(
/// TODO: create a proper drawer or appBar for the [StartTribeWidget]
appBar: AppBar(
title: const Text('Orkfia'),
),
body: const StartTribeWidget(),
);
// If the tribe `uid` value is `placeHolderTribe` assume that an error
// occurred while trying to get the tribe stream or while the tribe stream
// is parsed to [TribeSummary], log should give more information
} else if (tribe.uid == 'placeHolderErrorTribe') {
// TODO: create a bettter error screen for this situation
return const Center(
child: Text('Unable to retrieve tribe data'),
);
}
// This Scaffold wraps the entire app, anything here will be avilable
// globally
return Scaffold(
// App Bar
appBar: const AppBarContent(),
// [DrawerContentWidget] holds all the drawer content, it requires
// [selectedTab] function to handle the navigation between screens
drawer: DrawerContentWidget(
setSelectedTab: setSelectedTab,
selectedTabIndex: _drawerNavIndex,
),
// Display the contents of the selected screen
body: selectedTabContent(),
// Reserved
bottomNavigationBar: SizedBox(
height: 50,
child: Container(
color: Colors.red[100],
child: const Center(child: Text('Reserved space')),
)),
);
}
}
Use TextEditingController for every TextFormField to solve this problem.
A controller for an editable text field.
First Whenever the user modifies a text field with an associated TextEditingController, the text field updates value and the controller notifies its listeners. Listeners can then read the text and selection properties to learn what the user has typed or how the selection has been updated.
Second, remember to dispose of the TextEditingController inside dispose() when it is no longer needed. This will ensure we discard any resources used by the object.
To close keyboard from screen
you can use GesterDetector widget.
FocusManager.instance.primaryFocus?.unfocus();
or use can below for hot fix
FocusScope.of(context).unfocus();
Example is given below
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final TextEditingController _controller = TextEditingController();
#override
void initState() {
super.initState();
_controller.addListener(() {
final String text = _controller.text.toLowerCase();
_controller.value = _controller.value.copyWith(
text: text,
selection:
TextSelection(baseOffset: text.length, extentOffset: text.length),
composing: TextRange.empty,
);
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(6),
child: TextFormField(
controller: _controller,
decoration: const InputDecoration(border: OutlineInputBorder()),
),
),
);
}
}
When you navigate to a new page, you are not really disposing of the previous page. The new page is simply added on top of the previous page. You could try wrapping the entire scaffold in a GestureDetector with the following onTap function:
FocusScope.of(context).unfocus();
This will make sure the keyboard is dismissed when you push a new page with user taps.

Flutter Dropdown Button and Dropdown Handler different size

I want to create a dropdown menu on flutter where the handler button that opens the dropdown uses just an icon and the menu list opened by it uses an icon and a text.
I almost manage to create it, as you can check on the following screenshots:
Closed
Opened
I'm struggling with the opened width, so my question is how to give the opened menu enough width and keep the handler button on its current width.
Notice that I want the dropdown to be at the end of the Row, so consider this black box to be an area of something else, nothing important.
I'm adding the relevant code below and the complete code on the following links.
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Question Dropdown",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(
optionStream: BehaviorSubject<Option>(),
),
);
}
}
class HomePage extends StatelessWidget {
final BehaviorSubject<Option> optionStream;
const HomePage({
Key? key,
required this.optionStream,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Question Dropdown"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Expanded(
child: Container(
height: 48,
color: Colors.black,
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: StreamBuilder<Option>(
initialData: Option.A,
stream: optionStream,
builder: (context, snapshot) {
final option = snapshot.data ?? Option.A;
return _dropDownMenu(context, option);
},
),
),
],
),
],
),
),
);
}
Widget _dropDownMenu(
BuildContext context,
Option option,
) {
const items = Option.values;
return DropdownButtonHideUnderline(
child: DropdownButton<Option>(
value: option,
selectedItemBuilder: (context) =>
items.map((e) => _dropdownHandler(context, e)).toList(),
items: items.map((e) => _dropdownItem(context, e)).toList(),
onChanged: (e) => optionStream.add(e ?? Option.A),
),
);
}
OptionsItemHelper _dropDownItemData(
BuildContext context,
Option option,
) {
Widget icon;
String text;
switch (option) {
case Option.A:
icon = const Icon(Icons.ac_unit);
text = "An option";
break;
case Option.B:
icon = const Icon(Icons.baby_changing_station);
text = "Best option";
break;
case Option.C:
icon = const Icon(Icons.cake_sharp);
text = "Closest option";
break;
case Option.D:
icon = const Icon(Icons.dashboard);
text = "Dumb option";
break;
}
return OptionsItemHelper(text, icon);
}
Widget _dropdownHandler(
BuildContext context,
Option option,
) {
final helper = _dropDownItemData(context, option);
return helper.icon;
}
DropdownMenuItem<Option> _dropdownItem(
BuildContext context,
Option option,
) {
final helper = _dropDownItemData(context, option);
return DropdownMenuItem<Option>(
value: option,
child: Row(
children: [
helper.icon,
const SizedBox(width: 16),
Text(helper.text),
],
),
);
}
}
enum Option {
A,
B,
C,
D,
}
class OptionsItemHelper {
final String text;
final Widget icon;
OptionsItemHelper(
this.text,
this.icon,
);
}
Complete code on Github
Complete code on Gitlab
I did find a workaround using GestureDetector and showMenu, I'm sharing here and pushing to the repo as "workaround" commit in case you need the same as I need now, I'm keeping the question without answer in case someone finds a better way using the dropdown.
The new dropDownMenu function
Widget _dropDownMenu(
BuildContext context,
Option option,
) {
const items = Option.values;
return GestureDetector(
onTapDown: (details) async {
final offset = details.globalPosition;
final newOption = await showMenu(
context: context,
position: RelativeRect.fromLTRB(offset.dx, offset.dy, 0, 0),
items: items.map((e) => _dropdownItem(context, e, option)).toList(),
);
if (newOption != null) {
optionStream.add(newOption);
}
},
child: _dropdownHandler(context, option),
);
}
and the new dropdownItem function.
PopupMenuEntry<Option> _dropdownItem(
BuildContext context,
Option option,
Option selected,
) {
final helper = _dropDownItemData(context, option);
return CheckedPopupMenuItem<Option>(
value: option,
checked: option == selected,
child: Row(
children: [
Expanded(child: Container()),
Text(helper.text),
const SizedBox(width: 16),
helper.icon,
],
),
);
}
How it looks like
Closed
Opened
Bigger Screen

Flutter oddly not reloading widget

I have been working with flutter just a while now but I have never experienced such a weird problem. basically I'm checking if there is a logged on username show them the main page and if not show them the signup page. after the user signs up (and logs in at the same time) I want to take him to my main page. even though I return a new Scaffold the mobile screen doesn't change at all. not with a hot load or anything. but after stopping the program and running it again (because the user is logged in) it automatically goes to my main page (which I want to do without having to stop the program and running it again. any ideas why this is happening couldn't find anything related to this.
import 'package:sociagrowapp/models/user.dart';
import 'package:sociagrowapp/Authenticate/SignIn.dart';
import 'package:sociagrowapp/HomePages/Home.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Wrapper extends StatefulWidget{
#override
createState() => _Wrapper();
}
class _Wrapper extends State<Wrapper> {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
print(user);
// return either the Home or Authenticate widget
if (user == null){
print('Should Changed 3');
return Scaffold(
body: SignIn()
);
}
else {
print('Should Changed');
print('Should Changed2');
return PageData()
}
}
}
Just in case I will add the PagesData Code but I don't think it is related to that.
import 'package:flutter/material.dart';
import 'package:sociagrowapp/services/auth.dart';
int selectedbotnavi = 0;
class DailyTasks extends StatefulWidget
{
#override
createState() => _DailyTasks();
}
class _DailyTasks extends State<DailyTasks>
{
Widget build(BuildContext context)
{
return Center(
child: Text("15")
);
}
}
class Settings extends StatefulWidget
{
#override
createState() => _Settings();
}
class _Settings extends State<Settings>
{
String _httpdataretrieved;
Widget build(BuildContext context)
{
return Column(
children: <Widget>[
Container(width:MediaQuery.of(context).size.width,
child: Text('Your Account Username',style: TextStyle(fontWeight: FontWeight.w400),),
alignment: Alignment.center,
padding: EdgeInsetsDirectional.fromSTEB(0, 20, 0, 0),
),
Container(width:MediaQuery.of(context).size.width,
child: Text(' Important: Your Account must be public for SociaGrow. \n There are limited Features available to private Accounts',style: TextStyle(fontWeight: FontWeight.w900,fontSize:14),
),
alignment: Alignment.center,
padding: EdgeInsetsDirectional.fromSTEB(0, 5, 0, 20),
),
Container(child: TextField(
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Username',
),
),
width: MediaQuery.of(context).size.width * 0.8,
alignment: Alignment.center,
padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 15),
),
Container(
child: RaisedButton(
child: Text('Change Username'),
),
)
],
);
}
}
List <Widget> Pages = [new DailyTasks(),new DailyTasks(),new DailyTasks()];
class PageData extends StatefulWidget
{
#override
createState() => _PageData();
}
class _PageData extends State<PageData>
{
void _changeselectbotnaviindex(int index)
{
selectedbotnavi = index;
setState(() {
});
}
final AuthService _auth = AuthService();
#override
Widget build(BuildContext context)
{
return Scaffold(
appBar: AppBar(title: Container(
child: Image.asset('assets/Logo.png',width: 100,height: 200,),
padding: EdgeInsetsDirectional.fromSTEB(0, 10, 0 , 0),
),
actions: <Widget>[
FlatButton(
child: Text('Sign out'),
onPressed: () async {
await this._auth.signOut();
},
),
],
),
body: Pages[selectedbotnavi],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items :[
BottomNavigationBarItem(icon: Icon(Icons.timelapse),title:Text('Daily Tasks')),
BottomNavigationBarItem(icon: Icon(Icons.call_made),title:Text('Growth')),
BottomNavigationBarItem(icon: Icon(Icons.settings),title:Text('Settings')),],
currentIndex: selectedbotnavi,
onTap: _changeselectbotnaviindex,
selectedItemColor: Colors.amber[800],
unselectedItemColor: Colors.black,
showUnselectedLabels: true,
)
);
}
}
That is not the way you navigate to a new page in Flutter.
In Flutter, the way to navigate between pages is with Navigator, which is a widget that manages a set of child widgets with a stack discipline. That is, Navigator has everything ready for you to navigate between pages easily. When you create an app with MaterialApp, Flutter attaches a Navigator to the top of the widget tree for you under the hood. This way, you can access the Navigator in your widget tree via context, by calling Navigator.of(context).
In your case, when the user taps the sign up button in your sign up page, you should do something like:
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => PageData()));
This way, your app will navigate to PageData when the user signs in.
Check out this Flutter.dev article on navigating pages for more details on the topic.
You have to call SetState() so your build method is called again.
You could add a VoidListener to your SignIn(onLogin:(){setState(() {});})

Passing data between Widgets without using Navigator?

I've some struggling with flutter.
I've two widget Details screen so in the first Details, I have a list and I want to send it to the second Screen without using navigator.
So if there is anyone who can help me I will be very thankful.
Details :
List<String> _instructions = [];
class Details extends StatefulWidget {
#override
_DetailsState createState() => new _DetailsState();
}
class _DetailsState extends State<Details> {
Expanded(
child: Container(
margin: EdgeInsets.all(3),
child: OutlineButton(
highlightElevation: 21,
color: Colors.white,
shape: StadiumBorder(),
textColor: Colors.lightBlue,
child: Text(
'ENVOYER',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal,
color: Colors.lightBlue,
),
),
borderSide: BorderSide(
color: Colors.lightBlue,
style: BorderStyle.solid,
width: 1),
onPressed: () {
},
),
),
),
}}
so what I want is that,when I press the button I will send my list to the next widget without using navigator.
you can use the sharedpreferance which is the simple xml that belongs to the application. And this is how you can set it
Future<bool> setStringList(String key, List<String> value) =>
_setValue('StringList', key, value);
Future<bool> setStringList (
String key,
List<String> value
)
For more info here is a link
And you can get your List by
List<String> getStringList(String key) {
List<Object> list = _preferenceCache[key];
if (list != null && list is! List<String>) {
list = list.cast<String>().toList();
_preferenceCache[key] = list;
}
return list;
}
Also you can use sqflite
Suppose you have two pages, namely 'page1.dart' and 'page2.dart', both want to access the same list:
Create another dart file 'GlobalVariables.dart', inside this file, create a class gv.
Inside this class gv, create a static list by using:
static List <String> listAnyList = [];
import 'GlobalVariables.dart' in the 2 pages that need to access this list.
Now, in page1.dart and page2.dart,
you can use gv.listAnyList to access the 'Global List'.
Use 'Global Static Variables' if a variable is needed in many dart files, e.g. the 'User ID', then you can simply use gv.strUserID to access it in any pages you want.
I think a more appropriate approach should be similar to how android fragments connect to each other using bloc pattern or master-details flow.
I created an example repository to show the complete concept.
In short, the idea is to create a class with StreamController inside. Both widgets will have a reference to bloc instance. When the first widget wants to send data to the second it adds a new item to Stream. The second listen to the stream and updates its content accordingly.
Inside bloc:
StreamController<String> _selectedItemController = new BehaviorSubject();
Stream<String> get selectedItem => _selectedItemController.stream;
void setSelected(String item) {
_selectedItemController.add(item);
}
First fragment:
class FragmentList extends StatelessWidget {
final Bloc bloc;
const FragmentList(
this.bloc, {
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.selectedItem,
initialData: "",
builder: (BuildContext context, AsyncSnapshot<String> screenType) {
return ListView.builder(
itemBuilder: (context, index) {
return ListTile(
selected: bloc.items[index] == screenType.data,
title: Text(bloc.items[index]),
onTap: () {
bloc.setSelected(bloc.items[index]);
},
);
},
itemCount: bloc.items.length,
);
},
);
}
}
The second fragment:
class FragmentDetails extends StatelessWidget {
final Bloc bloc;
const FragmentDetails(
this.bloc, {
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: StreamBuilder(
initialData: "Nothing selected",
stream: bloc.selectedItem,
builder: (BuildContext context, AsyncSnapshot<String> screenType) {
final info = screenType.data;
return Text(info);
},
),
);
}
}