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);
},
),
);
}
}
Related
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),
)
],
);
}
}
Currently, I have two sample files Parent.dart and Child.dart.
In Parent.dart file this is what the code is like:
Parent.dart file:
children:
[
isDisabled
? Icon(Icons.public, color: Colors.grey)
: Icon(Icons.public, color:Colors.white),
InkWell(
onTap:()=> Navigator.push(context,MaterialPageRoute(builder: (context)=> Child(
isDisabled: isDisabled, function: ()=> function())),
]
function()
{
setState(()=> isDisabled = !isDisabled);
}
and in Child.dart the code is something like this:
children:
[
widget.isDisabled
? Icon(Icons.public, color: Colors.grey)
: Icon(Icons.public, color:Colors.white),
InkWell(
onTap:()=> widget.function(),
]
I have some data being fetched from a server that is used to populate a list of cards inside listview.builder.
What I'm trying to do is inherent variables from the parent and use their value to update the child. Currently, if I run this parent does change, but the child doesn't until you navigate back from parent to child.
For a better context: Imagine a list of cards. Each has an add-to-list button. Now if you click on the card it goes to another screen "child.dart" where it gives you more details about the item on the card you clicked. Now if you click the add-to-list button on the child screen it should also update the parent.
I tried different ways of achieving this "UI synchrony" for a better user experience. But I didn't find a proper way to implement it.
Things I tried: Provider (but it updates all the items on the list instead of each instance.),
a "hacky" method of editing the data in the list on the client side and updating the widget based on that. (This technique does work, but ewwwww)
I'm not really sure to understand your question.
If your question is how trigger a function in parent from child screen, here is your answer.
I made a working example. I think you were really close.
Another option for state management is riverpod 2.0
Or you can pass value in Navigator.pop and trigger the function in parent.
Parent model
class Parent {
String title;
bool isDisabled = false;
Parent({required this.title});
}
Main.dart
import 'package:flutter/material.dart';
import 'parent.dart';
import 'ParentCard.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
MyApp({super.key});
List<Parent> parentList = [Parent(title: 'Item 1'), Parent(title: 'Item 2')];
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: ListView.builder(
itemCount: parentList.length,
itemBuilder: (BuildContext context, int position) {
return ParentCard(title: parentList[position].title);
},
),
),
);
}
}
ParentCard.dart
import 'package:flutter/material.dart';
import 'child.dart';
class ParentCard extends StatefulWidget {
String title;
ParentCard({super.key, required this.title});
#override
State<ParentCard> createState() => _ParentCardState();
}
class _ParentCardState extends State<ParentCard> {
bool isDisabled = false;
#override
Widget build(BuildContext context) {
return Row(
children: [
Text(widget.title),
isDisabled
? Icon(Icons.public, color: Colors.green)
: Icon(Icons.public, color: Colors.black),
IconButton(
icon: Icon(Icons.plus_one),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ChildCard(isDisabled: isDisabled, handler: handler)),
),
)
],
);
}
handler() {
setState(() => isDisabled = !isDisabled);
}
}
** child.dart**
import 'package:flutter/material.dart';
class ChildCard extends StatefulWidget {
VoidCallback handler;
bool isDisabled;
ChildCard({super.key, required this.isDisabled, required this.handler});
#override
State<ChildCard> createState() => _ChildCardState();
}
class _ChildCardState extends State<ChildCard> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: [
const Text('child !'),
widget.isDisabled
? const Icon(Icons.public, color: Colors.green)
: const Icon(Icons.public, color: Colors.black),
ElevatedButton(
onPressed: () {
setState(() {
widget.isDisabled = !widget.isDisabled;
});
widget.handler();
},
child: const Text('click to trigger'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('pop it'),
)
]),
);
}
}
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();
}
}
Am a completely new flutter dev. I am trying to save a document from a queried firestore list on another saved documents page like an add to cart functionality. Am passing doc id as arguments to another page from firestore so that I get data based on the previous selection. Now how can I send the firestore reference and save it to the other screen without navigating to it so that users are able to save their favorite docs on another page and access them? Here is my Assignment page that lists the docs based on the previous selection.
class Assignments extends StatelessWidget {
final String programId;
final String yearId;
final String semesterId;
final String courseId;
const Assignments(
{Key key, this.programId, this.yearId, this.semesterId,
this.courseId})
: super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar2(title: 'Assigment'.toUpperCase(), ),
body: Column(
children: [
Expanded(
child: ContentArea(
addPadding: false,
child: StreamBuilder(
stream:
getAssignment(programId, yearId, semesterId, courseId),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(
color: kOnSurfaceTextColorYellow),
);
}
return ListView.separated(
padding: UIParameters.screenPadding,
shrinkWrap: true,
itemCount: snapshot.data.docs.length,
itemBuilder: (BuildContext context, int index) {
final data = snapshot.data.docs[index];
return DisplayCard(
title: data['nameOfAssignment'],
icon: Icons.add,
// Here is the action i want that should save the documment to
// the SavedPage empty list without navigating to it
onTapIconSave: (){}
onTap: () => Get.to(Pdf(
nameOfAssignment: data['nameOfAssignment'],
pdfUrl: data['pdfUrl'],
)),
);
},
separatorBuilder: (BuildContext context, int index) {
return const SizedBox(
height: 10,
);
},
);
})),
),
],
),
);
}
}
Here is the SavedPage which may be similar to the cart page. Am not sure what to do in order to save the Document from the Assignment Page in a Dynamic growable list
class Saved extends StatefulWidget {
const Saved({ Key key }) : super(key: key);
#override
State<Saved> createState() => _SavedState();
}
class _SavedState extends State<Saved> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CustomAppBar2(title: 'Saved'),
body: Column(
children: [],
),
);
}
}
You can add a state management package like Provider or Bloc, also you could save your data in your local database and access them from there. I recommend Provider, easy to use, and its what you need.
I am totally flutter beginner.
What I want to do is pass the data (by TextController) from StatefulWidget to another one.
Here is my code (passive Widget)
import 'package:flutter/material.dart';
class VocabularyText extends StatefulWidget {
final String text;
// ignore: sort_constructors_first
const VocabularyText ({ Key key, this.text }): super(key: key);
#override
_VocabularyTextState createState() => _VocabularyTextState();
}
class _VocabularyTextState extends State<VocabularyText> {
Offset offset = Offset.zero;
#override
Widget build(BuildContext context) {
return Container(
child: Positioned(
left: offset.dx,
top: offset.dy,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
offset = Offset(
offset.dx + details.delta.dx, offset.dy + details.delta.dy);
});
},
child: const SizedBox(
width: 300,
height: 300,
child: Padding(
padding: EdgeInsets.all(8),
child: Center(
child: Text(
'a',
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 28,
color: Colors.red
)
),
),
),
)),
),
);
}
}
The thing is here
child: Text(
//
widget.text,
//
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 28,
color: Colors.red
)
),
According to my research, this should work, but it doesn't. Why did I make a mistake?
Here is references
How to add a draggable "textfield" to add text over images in flutter?
Passing Data to a Stateful Widget
Thank you in advance.
edit
i answered before seeing the image it wasn't there
after seeing the image
what causing the problem is this
widget.text
the correct way to use it in Text widget is like this
Text('${widget.text}'),
i would suggest that you do the following
to send data to Statefull Widget first you use Navigator or any other method to open this widget to the user like so
return GestureDetector(
onTap: () => {
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
//note here between the () you pass the variable
Categories(companies[index].id, companies[index].name)),
)},
and then to receive them you do like this in my case its Categories Class
class Categories extends StatefulWidget {
//getting company id from home page
final int companyId;
final companyName;
Categories(this.companyId , this.companyName);
#override
_CategoriesState createState() => _CategoriesState();
}
class _CategoriesState extends State<Categories> {
#override
Widget build(BuildContext context) {
...... rest of the code
and now to use the data you can do like this for example
widget.companyId
this was an example from my code now lets jump to your code
to receive the text from the text editing controller you do
class TextReceiver extends StatefulWidget {
//getting company id from home page
final String userInput;
TextReceiver(this.userInput);
#override
TextReceiver createState() => _TextReceiver();
}
//to use it
widget.userInput
now to send it you send it through Material Navigator
onTap: () => {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => TextReceiver(TextEditingController.text)),
)},
note that you should pass it as TextEditingController.text because the constructor in TextReceiver is specifying the type to String if you passed TextEditingController then the type wouldn't be String it will be TextEditingController type
all of this code is for example and it would't be like your code but it will give you the idea
refer to official docs https://flutter.dev/docs/cookbook/navigation/passing-data
Edit : remove const from this line
child: const SizedBox(
rest of the code
)
to this
child: SizedBox(
rest of the code
)