Flutter streambuilder does not update a statefulwidget or statelesswidget - flutter

I'm trying to build a streambuilder.
My problem is that the streambuilder does not update other statefulWidgets inside the streambuilder widget.
Here my code:
class ChatPage extends StatefulWidget {
//pass parm
var chat_object;
ChatPage(this.chat_object, {Key? key}) : super(key: key);
#override
_ChatPage createState() => _ChatPage();
}
class _ChatPage extends State<ChatPage> {
//pass parm
var chat_object;
_ChatPage(this.chat_object);
#override
Widget build(BuildContext context) {
// Full screen width and height
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
// Height (without SafeArea)
var padding = MediaQuery.of(context).padding;
double height1 = height - padding.top - padding.bottom;
// Height (without status bar)
double height2 = height - padding.top;
// Height (without status and toolbar)
double height3 = height - padding.top - kToolbarHeight;
//scaffold
return Scaffold(
appBar: customAppBarGoBack('Chat', context),
body: LimitedBox(
maxHeight: height3,
child: Stack(
children: [
StreamBuilder<dynamic>(
stream: FirebaseFirestore.instance
.collection('chat')
.doc('chat_messages')
.collection(chat_object.chat_message_id[0].trim())
.orderBy('message_time')
.snapshots(),
builder: (
BuildContext context,
AsyncSnapshot<dynamic> snapshot,
) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else {
if (snapshot.hasError) {
return const Text('Error');
} else if (snapshot.hasData) {
//chat room object list
var chat_room_object =
load_object(snapshot.data, chat_object).first;
//chat message object list
var message_object =
load_object(snapshot.data, chat_object).last;
print('message_object: ' + message_object.toString());
return Test(chat_room_object, message_object);
} else {
return const Text('Empty data');
}
}
},
),
],
),
),
drawer: CustomSideBar(context),
);
}
}
//test
class Test extends StatefulWidget {
//pass parm
var chat_room_object;
var message_object;
Test(this.chat_room_object, this.message_object, {Key? key})
: super(key: key);
#override
_Test createState() => _Test(chat_room_object, message_object);
}
class _Test extends State<Test> {
//pass parm
_Test(this.chat_room_object, this.message_object);
var chat_room_object;
var message_object;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: message_object.length,
itemBuilder: (context, i) {
return Text(message_object[i].message.toString());
},
);
}
}
If I build a list inside the streambuilder the list will be update.
But if I build the list inside a new widget and pass the values to list the widget will be not update.
Anyone can give me a simple example how I can update the values in a statefulwidget which is contain in a streambuilder?

Instead of passing parameters in the constructor of state of Test widget, can you try to access them directly from the widget.Example as below.
//test
class Test extends StatefulWidget {
//pass parm
var chat_room_object;
var message_object;
Test(this.chat_room_object, this.message_object, {Key? key})
: super(key: key);
#override
_Test createState() => _Test();
}
class _Test extends State<Test> {
//pass parm
_Test();
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: widget.message_object.length,
itemBuilder: (context, i) {
return Text(widget.message_object[i].message.toString());
},
);
}
}

Related

After I pick pictures or take a picture, when selected pictures go out side of screen, pictures keep disappearing

I made vertical scroll list of question cards with selected pictures at the end of list using image_picker with scroll_to_index. I can see pictures right after I picked the pictures but when I scroll up the list, the selected pictures keep disappearing. It seems like the imageList in child widget does not maintain changed values of list. here is my short code.
parent.dart
class Parent extends StatefulWidget {
const Parent({Key? key}) : super(key: key);
#override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
#override
Widget build(BuildContext context) {
return Scaffold(body: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: ListView.builder(
padding: EdgeInsets.all(8),
itemCount: _list.length + 1,
itemBuilder: ((context, index) {
if (index != _list.length) {
return AutoScrollTag( //scroll_to_index
key: ValueKey(index), //scroll_to_index
controller: scrollController, //scroll_to_index
index: index,
highlightColor: Colors.blue.withOpacity(0.3),
child: Container(
...
}else(){
return Child();
}
));
}
}
child.dart
class Child extends StatefulWidget {
const Child({Key? key}) : super(key: key);
#override
State<Child> createState() => _ChildState();
}
class _ChildState extends State<Child> {
List<String> imageList = [];
pickMultiImages() async {
final List<PickedFile>? selectedImages =
await ImagePicker.platform.pickMultiImage();
if (selectedImages != null) {
imageList.addAll(selectedImages);
}
setState(() {});
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Wrap(
children: [
if (imageList.length > 0)
for (int i = 0; i < imageList.length; i++)
Stack(children: [
Column(
...
,
IconButton(onPressed: (() => {
pickMultiImages()
}), icon: Icon(Icons.add))
],
);
this code does not work.
but When I added List<PickedFile> imageList = [] and pass to child widget as parameter in Parent Widget and in Child Widget,
class Child extends StatefulWidget {
List<PickedFile> imageList = []; // added
Child({
Key? key,
required this.imageList, // added
}) : super(key: key);
...
pickMultiImages() async {
...
widget.imageList.addAll(selectedImages); // imageList.addAll -> widget.imageList.addAll
...
setState(() {});
}
this one worked. Should I always declare the changing child state in parent widget? or this is just special case that i have to do like this?

how to display the value that came from valueNotifier?

I have a valueNotifier that generates a list of events and takes a random string every 5 seconds and sends it to the screen. It lies in inheritedWidget. How can I display in the ListView the event that came with the valueNotifier? What is the correct way to print the answer?
My code:
class EventList extends StatelessWidget {
const EventList({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return EventInherited(
child: EventListScreen(),
);
}
}
class EventListScreen extends StatefulWidget {
const EventListScreen({Key? key}) : super(key: key);
#override
State<EventListScreen> createState() => _EventListScreenState();
}
class _EventListScreenState extends State<EventListScreen> {
#override
Widget build(BuildContext context) {
final eventNotifier = EventInherited.of(context).eventNotifier;
return Scaffold(
appBar: AppBar(
title: const Text('Event List'),
centerTitle: true,
),
body: Container(
padding: const EdgeInsets.all(30),
child: ValueListenableBuilder(
valueListenable: eventNotifier,
builder: (BuildContext context, List<String> value, Widget? child) {
return ListView(
children: [
],
);
},
),
),
);
}
}
class EventNotifier extends ValueNotifier<List<String>> {
EventNotifier(List<String> value) : super(value);
final List<String> events = ['add', 'delete', 'edit'];
final stream = Stream.periodic(const Duration(seconds: 5));
late final streamSub = stream.listen((event) {
value.add(
events[Random().nextInt(4)],
);
});
}
class EventInherited extends InheritedWidget {
final EventNotifier eventNotifier = EventNotifier([]);
EventInherited({required Widget child}) : super(child: child);
static EventInherited of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType()!;
}
#override
bool updateShouldNotify(EventInherited oldWidget) {
return oldWidget.eventNotifier.streamSub != eventNotifier.streamSub;
}
}
If you have correct value, you can return listview like this:
return ListView.builder(
itemCount: value.length,
itemBuilder: (context, index) {
return Text(value[index]);
},
);
After having a quick look at ValueNotifier,
It says the following:
When the value is replaced with something that is not equal to the old value as evaluated by the equality operator ==, this class notifies its listeners.
In your case, the value is an array. By adding items to the array, it wont recognise a change.
Also see other Stacko post.
Try something like:
value = [...value].add(...)

Flutter | Pass value from child to parent

I need a DropdownButton with items depending on another DropdownButton. Sounds a bit confusing but it isnt. Here is my code with comments at the important parts in order to understand my intention.
Parent
class Parent extends StatefulWidget {
const Parent({ Key? key }) : super(key: key);
#override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
#override
Widget build(BuildContext context) {
return SafeArea(
child: SizedBox(
width: 500,
height: 500,
child: Column(
children: const [
// Main
DropDownWidget(collection: "MainCollection",),
// Depending
DropDownWidget(collection: ""), // Collection should equals value from Main DropDownWidget
],
),
),
);
}
}
Child
class DropDownWidget extends StatefulWidget {
final String collection;
const DropDownWidget({Key? key, required this.collection}) : super(key: key);
#override
State<DropDownWidget> createState() => _DropDownWidgetState();
}
class _DropDownWidgetState extends State<DropDownWidget> {
var selectedItem;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection(widget.collection)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.hasError) {
return const CircularProgressIndicator();
} else {
var length = snapshot.data?.docs.length;
List<DropdownMenuItem<String>> items = [];
for (int i = 0; i < length!; i++) {
DocumentSnapshot snap = snapshot.data!.docs[i];
items.add(DropdownMenuItem(
child: Text(snap.id),
value: snap.id,
));
}
return DropdownButtonFormField<String>(
onChanged: (value) {
setState(() {
selectedItem = value;
// ********************
// PASS value TO PARENT
// ********************
});
},
value: selectedItem,
items: items);
}
});
}
}
When the Main DropdownButton changes its value, it should pass that to my parent in order to change the focused collection of my depending DropdownButton. I already solved that problem by throwing all the code in one class buts that not the way I want to go.
So maybe you can help me out :)
Thanks
Create an argument ValueChanged<String> onSelectItem in your child. Call the method when the value changes.
Then in your parent, you provide a function that needs to be called when the value changes in your child.

Is there a way to rebuild AnimatedList in Flutter?

I have the following issue with my 'workout' App using multiple workoutlists with various workoutitems:
I select a workoutlist with 12 workoutitems.
The 'activity' screen with the AnimatedList is shown.
Afterwards, I select a different workoutlist with 80 workoutitems.
The AnimatedList is now showing the new workoutlist but only the first 12 workoutitems.
Why?
I thought that the AnimatedList inside the build Widget is rebuild every time (I am not using GlobalKey).
class WorkoutListView extends StatelessWidget {
const WorkoutListView({this.filename});
final String filename;
#override
Widget build(BuildContext context) {
return Selector<WorkoutListModel, List<Workout>>(
selector: (_, model) => model.filterWorkouts(filename),
builder: (context, workouts, _) {
return AnimatedWorkoutList(
list: workouts,
);
},
);
}
}
class AnimatedWorkoutList extends StatefulWidget {
const AnimatedWorkoutList({
Key key,
#required List<Workout> list,
}) : _list = list,
super(key: key);
final List<Workout> _list;
#override
_AnimatedWorkoutListState createState() => _AnimatedWorkoutListState();
}
class _AnimatedWorkoutListState extends State<AnimatedWorkoutList> {
#override
Widget build(BuildContext context) {
return AnimatedList(
initialItemCount: widget._list.length,
itemBuilder: (context, index, animation) {
final workout = widget._list[index];
return Column(
children: [
// Using AnimatedList.of(context).removeItem() for list manipulation
],
);
},
);
}
}
try this:
class AnimatedWorkoutList extends StatefulWidget {
const AnimatedWorkoutList({
#required List<Workout> list,
});
final List<Workout> list;
#override
_AnimatedWorkoutListState createState() => _AnimatedWorkoutListState();
}
class _AnimatedWorkoutListState extends State<AnimatedWorkoutList> {
#override
Widget build(BuildContext context) {
return AnimatedList(
initialItemCount: widget.list.length,
itemBuilder: (context, index, animation) {
final workout = widget.list[index];
return Column(
children: [
// Using AnimatedList.of(context).removeItem() for list manipulation
],
);
},
);
}
}

Flutter Send Data To Other Screen Without Navigator

I have question how to pass data between pages/screen in flutter without navigator and only using onChanged and streambuilder.
All I want is whenever user write in textfield on first widget, the second widget automatically refresh with the new data from first widget.
Here's my code for first.dart
import 'package:flutter/material.dart';
import 'second.dart';
class First extends StatefulWidget {
First({Key key}) : super(key: key);
#override
_FirstState createState() => _FirstState();
}
class _FirstState extends State<First> {
final TextEditingController _myTextController =
new TextEditingController(text: "");
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: Text("Passing Data"),
),
body: Column(
children: <Widget>[
Container(
height: 120.0,
child: Column(
children: <Widget>[
TextField(
controller: _myTextController,
onChanged: (String value) {
// refresh second with new data
},
)
]
)
),
Container(
height: 120.0,
child: Second(
myText: _myTextController.text,
),
)
],
),
);
}
}
and here's my second.dart as second widget to receive data from first widget.
import 'dart:async';
import 'package:flutter/material.dart';
import 'api_services.dart';
class Second extends StatefulWidget {
Second({Key key, #required this.myText}) : super(key: key);
final String myText;
#override
_SecondState createState() => _SecondState();
}
class _SecondState extends State<Second> {
StreamController _dataController;
loadPosts() async {
ApiServices.getDetailData(widget.myText).then((res) async {
_dataController.add(res);
return res;
});
}
#override
void initState() {
_dataController = new StreamController();
loadPosts();
super.initState();
print(widget.myText);
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _dataController.stream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
return Text(snapshot.error);
}
if (snapshot.hasData) {
return Container();
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Row(
children: <Widget>[
Text("Please Write A Text"),
],
);
} else if (snapshot.connectionState != ConnectionState.active) {
return CircularProgressIndicator();
}
if (!snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
return Text('No Data');
} else if(snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
return Text(widget.myText);
}
return null;
});
}
}
You have a couple of options. The two simplest are - passing the text editing controller itself through to the second widget, then listening to it and calling setState to change the text in the second widget.
Example
class Second extends StatefulWidget {
Second({Key key, #required this.textController}) : super(key: key);
final TextEditingController textController;
#override
_SecondState createState() => _SecondState();
}
class _SecondState extends State<Second> {
// made this private
String _myText;
#override
void initState() {
_myText = widget.textController.text
widget.textController.addListener(() {
setState((){_myText = widget.textController.text});
);
});
super.initState();
}
...
// then in your build method, put this in place of return Text(widget.myText);
return Text(_myText);
Option 2 is listening to the controller in your first widget and call setState in there. This will rebuild both the first and second widget though, and I think is not as performant as the first option.
Hope that helps