Persistent bottom sheet form data - forms

I have one form on a bottom sheet. It's opened on click of one button. It can be closed when the user clicks outside the form. I want to maintain the form data if the user reopens the form. I don't want to assign each form field value explicitly. Is there any other way of saving the form state and reusing it while creating the bottom sheet again?
void _modalBottomSheetMenu(BuildContext context, Widget form) async {
await showModalBottomSheet<dynamic>(
isDismissible: false,
isScrollControlled:true,
context: context,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)),
),
backgroundColor: Colors.white,
builder: (BuildContext bc) {
return SingleChildScrollView(
child: Container(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Padding(
padding: const EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 0.0),
child: form) // From with TextField inside
));}
);

So I finally found one of the best approaches for maintaining the state using Provider. I also explored other ways such as BLOC but it was very verbose for it. We can use BLOC for other cases but Provider is a better solution in case of a bottom sheet.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Bottom Sheet',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ChangeNotifierProvider(
create: (context) => TitleDataNotifier(),
child: ButtomSheetScreen(),
),
);
}
}
class TitleDataNotifier with ChangeNotifier {
String _name;
String get name => _name;
set name(String name) {
_name = name;
notifyListeners();
}
}
class AddTaskScreen extends StatefulWidget {
final TitleDataNotifier valueProvider;
AddTaskScreen(this.valueProvider);
#override
_AddTaskScreenState createState() => _AddTaskScreenState();
}
class _AddTaskScreenState extends State<AddTaskScreen> {
final _controller = TextEditingController();
#override
void initState() {
super.initState();
_controller.text = widget.valueProvider.name;
}
void dispose() {
_controller.dispose();
super.dispose();
}
#override
void deactivate() {
widget.valueProvider.name = _controller.text;
super.deactivate();
}
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0),
topRight: Radius.circular(20.0),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
'Add Task',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 30.0,
color: Colors.lightBlueAccent,
fontWeight: FontWeight.w700),
),
TextField(
controller: _controller,
autofocus: false,
textAlign: TextAlign.center,
onChanged: (newText) {
widget.valueProvider._name = newText;
},
),
FlatButton(
child: Text(
'Add',
style: TextStyle(color: Colors.white),
),
color: Colors.lightBlueAccent,
onPressed: () {
Navigator.pop(context);
},
)
],
),
);
}
}
class ButtomSheetScreen extends StatelessWidget {
void openBottomSheet(context) {
var valueProvider = Provider.of<TitleDataNotifier>(context, listen: false);
showModalBottomSheet<dynamic>(
context: context,
builder: (BuildContext context) {
return ChangeNotifierProvider.value(
value: valueProvider,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter state) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: Wrap(
children: <Widget>[
AddTaskScreen(valueProvider),
],
),
);
}),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
"Bottom Sheet",
),
),
body: Center(
child: Container(
child: IconButton(
icon: const Icon(Icons.work),
onPressed: () {
openBottomSheet(context);
},
)),
));
}
}

Related

Does anyone know what's going on with my Listview?

I needed to insert a TextField to search/filter records, but I don't know what's going on.
When I click on the "Cães" option of the BottomNavigationBar, on main.dart,
I only get a CircularProgressIndicator and the data does show up.
Have any of you experienced this problem?
Does anyone know why my Listview doesn't show up?
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:ssk_final/addeditpage.dart';
//import 'package:flutter_localizations/flutter_localizations.dart';
List<dynamic> list = [];
class CaesPage extends StatefulWidget {
// CaesPage({Key key}) : super(key: key);
#override
_CaesPageState createState() => _CaesPageState();
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text("Cadastro de Cães"),
),
);
}
}
class _CaesPageState extends State<CaesPage> {
String searchString = "";
Future<List<Caes>> caes;
Future getData() async {
var url = 'http://.../api2.php?opcao=read';
var response = await http.get(Uri.parse(url));
return json.decode(response.body);
}
/*
Future _showMyDialog(id, nome) async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button
builder: (BuildContext context) {
return AlertDialog(
title: Text('Exclusão'),
content: SingleChildScrollView(
child: Column(
children: <Widget>[
Text('Confirma a exclusão de ' + nome + '?'),
],
),
),
actions: <Widget>[
TextButton(
child: Text('Confirma'),
onPressed: () {
setState(() {
var url = 'http://.../api.php?opt=delete';
http.post(Uri.parse(url), body: {
'id': id,
});
});
Navigator.pop(context, true);
},
),
TextButton(
child: Text('Cancelar'),
onPressed: () {
Navigator.pop(context);
},
),
],
);
},
);
}
*/
#override
void initState() {
super.initState();
caes = fetchCaes();
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
backgroundColor: Color.fromRGBO(1, 87, 155, 1),
focusColor: Colors.blue,
foregroundColor: Colors.white,
hoverColor: Colors.green,
splashColor: Colors.tealAccent,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddEditPage(),
),
);
debugPrint('Clicked FloatingActionButton Button');
},
),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Divider(),
//SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0),
child: TextField(
onChanged: (value) {
setState(() {
searchString = value.toLowerCase();
});
},
decoration: const InputDecoration(
//contentPadding: EdgeInsets.symmetric(vertical: 10),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue, width: 1.0),
borderRadius: BorderRadius.all(Radius.circular(25.0))),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue, width: 1.0),
borderRadius: BorderRadius.all(Radius.circular(25.0)),
),
labelText: 'Pesquisa',
suffixIcon: Icon(Icons.search))),
),
SizedBox(height: 10),
Expanded(
child: FutureBuilder<List<Caes>>(
builder: (context, snapshot) {
if (snapshot.hasData) {
return Center(
child: ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return snapshot.data[index].nome
.toLowerCase()
.contains(searchString)
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new InkWell(
onTap: () {
print(index);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddEditPage(
caes: snapshot.data,
index: index,
),
),
);
},
child: new Container(
child: Column(
children: [
Text(
(snapshot.data[index].nome),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold),
),
Text(
('${snapshot.data[index].microchip}'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
),
),
Text(
('${snapshot.data[index].pedigree}'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
),
),
Text(
(snapshot
.data[index].data_nascimento),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
),
),
Text(
(snapshot.data[index].sexo),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
),
),
Text(
(snapshot.data[index].castrado),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
),
),
],
),
),
),
])
: Container();
},
separatorBuilder: (BuildContext context, int index) {
return snapshot.data[index].nome
.toLowerCase()
.contains(searchString)
? Divider()
: Container();
},
),
);
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
return Center(
child: CircularProgressIndicator(),
);
},
// future: list,
),
),
],
),
);
}
}
class Caes {
final int id;
final String nome;
final int microchip;
final int pedigree;
final String data_nascimento;
final String castrado;
final String sexo;
Caes({
this.id,
this.nome,
this.microchip,
this.pedigree,
this.data_nascimento,
this.castrado,
this.sexo,
});
factory Caes.fromJson(Map<String, dynamic> json) {
return Caes(
id: json['id'],
nome: json['nome'],
microchip: json['microchip'],
pedigree: json['pedigree'],
data_nascimento: json['data_nascimento'],
castrado: json['castrado'],
sexo: json['sexo'],
);
}
}
class Titulos {
Titulos({this.data, this.titulo, this.exposicao});
// non-nullable - assuming the score field is always present
final String data;
final String titulo;
final String exposicao;
factory Titulos.fromJson(Map<String, dynamic> json) {
final data = json['data'] as String;
final titulo = json['titulo'] as String;
final exposicao = json['exposicao'] as String;
return Titulos(data: data, titulo: titulo, exposicao: exposicao);
}
Map<String, dynamic> toJson() {
return {
'data': data,
'titulo': titulo,
'exposicao': exposicao,
};
}
}
Future<List<Caes>> fetchCaes() async {
final response = await http.get(Uri.parse('http://.../api.php?opt=read'));
if (response.statusCode == 200) {
var caesJson = jsonDecode(response.body) as List;
return caesJson.map((caes) => Caes.fromJson(caes)).toList();
} else {
throw Exception('Failed to load Caes');
}
}
Screen
I cant really provide an answer in your list view cause it needs more files to run for me. However I can provide you a nice way to search in a list for items and update it with a text field. You can copy and run the code in the main of a test project to see how it is working.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final StreamController<List<String>> _exercisesStreamController =
StreamController<List<String>>();
late Stream<List<String>> _exercisesStream;
final List<String> _exercises = [
"Running",
"Swimming",
"Football",
"Basketball",
"Volleyball",
"Karate",
"Ski",
"Snowboard",
"Baseball",
"Running1",
"Swimming1",
"Football1",
"Basketball1",
"Volleyball1",
"Karate1",
"Ski1",
"Snowboard1",
"Baseball1",
"Running2",
"Swimming2",
"Football2",
"Basketball2",
"Volleyball2",
"Karate2",
"Ski2",
"Snowboard2",
"Baseball2",
"Running3",
"Swimming3",
"Football3",
"Basketball3",
"Volleyball3",
"Karate3",
"Ski3",
"Snowboard3",
"Baseball3",
];
#override
void initState() {
super.initState();
_exercisesStreamController.sink.add(_exercises);
_exercisesStream = _exercisesStreamController.stream.asBroadcastStream();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: Column(
children: [
TextFormField(
maxLines: 1,
style: TextStyle(color: Colors.white),
onChanged: (String value) async {
List<String> temp = List.from(_exercises);
temp.removeWhere((element) =>
!element.toLowerCase().contains(value.toLowerCase()));
_exercisesStreamController.sink.add(temp);
},
decoration: InputDecoration(
prefixIcon: Icon(
Icons.search,
color: Colors.white,
),
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(15.0),
),
contentPadding: EdgeInsets.only(left: 15),
filled: true,
fillColor: Colors.blueGrey,
hintText: "search",
hintStyle: TextStyle(
color: Colors.white,
),
),
),
_listViewWidget()
],
),
),
);
}
Widget _listViewWidget() {
return Expanded(
child: StreamBuilder<List<String>>(
initialData: [],
stream: _exercisesStream,
builder: (context, snapshot) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: Colors.blueGrey,
borderRadius: BorderRadius.all(
Radius.circular(15),
),
),
padding: EdgeInsets.all(15),
margin: EdgeInsets.symmetric(vertical: 10),
child: Center(
child: Text(
snapshot.data![index],
style: TextStyle(color: Colors.white),
),
),
);
});
},
),
);
}
}
If you need further instructions i am happy to help.

Issue with provider when navigating between screens

I'm trying to learn more about provider but I'm facing an issue with an app I'm developing, specifically when I navigate to a screen. When I press on the button to take me to the history_screen is when I get an error from Provider, all the providers are declared at the top of the tree so not really sure why there's still an error.
Hopefully someone can help me!
See my code below and the error I get:
Main.dart
void main() {
runApp(
MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<List<Meds>>.value(
value: MedicinesNotifier().medicinesStream(),
initialData: MedicinesNotifier().meds,
updateShouldNotify: (_, __) => true),
//ChangeNotifierProvider<MedicinesNotifier>(create: (_) => MedicinesNotifier()),
ProxyProvider<List<Meds>,MedicinesNotifier>(
update:
(BuildContext context, List<Meds> meds1, MedicinesNotifier? medNoti) => MedicinesNotifier.med(meds1)
),
ChangeNotifierProvider<SearchHistoryData>(create: (_) => SearchHistoryData()),
],
child: MaterialApp(
title: 'Flutter Demo',
/*theme: ThemeData(
primarySwatch: Colors.blueGrey,
),*/
home: MyHomePage(),
),
);
}
}
home_page.dart
class _MyHomePageState extends State<MyHomePage> {
List<Meds> medFiltered = [];
TextEditingController searchController = new TextEditingController();
String searchText = "";
#override
Widget build(BuildContext context) {
bool isSearching = searchController.text.isNotEmpty;
return Scaffold(
bottomNavigationBar: BottomBar(),
body: SafeArea(
child: Container(
padding: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
Container(
child: TextField(
controller: searchController,
decoration: InputDecoration(
labelText: "Search",
border: new OutlineInputBorder(
borderSide: new BorderSide(
color: Theme.of(context).primaryColor,
),
borderRadius: BorderRadius.circular(30.0),
),
prefixIcon: Icon(Icons.search),
),
onChanged: (value) {
Provider.of<MedicinesNotifier>(context, listen: false)
.changeSearchString(value);
},
),
),
Expanded(
child:
ListView.builder(
shrinkWrap: true,
itemCount: isSearching == true
? context
.watch<MedicinesNotifier>()
.meds
.length
: context
.watch<List<Meds>>()
.length,
itemBuilder: (context, index) {
Meds med = isSearching == true
? context
.watch<MedicinesNotifier>()
.meds
[index]
: context
.watch<List<Meds>>()[index]; //medsStreamed[index];
return ListTile(
onTap: () {
context.read<MedicinesNotifier>().addHistory(med);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatScreen(
med: context.watch<MedicinesNotifier>().findMedicine(med),
)));
},
title: Text(med.name),
subtitle: Text(med.description.substring(0, 10)),
);
},
),
),
],
),
),
),
);
}
}
BottomBar widget
class BottomBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BottomAppBar(
shape: CircularNotchedRectangle(),
color: Theme.of(context).primaryColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
icon: Icon(Icons.search),
color: Colors.white,
iconSize: 40.0,
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => MyHomePage()));
},
),
IconButton(
color: Colors.white,
icon: Icon(Icons.history_edu),
iconSize: 40.0,
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => HistoryScreen()));
},**//Causing the issue**
),
]),
);
}
}
history_screen.dart
class HistoryScreen extends StatefulWidget {
const HistoryScreen({Key? key}) : super(key: key);
#override
_HistoryScreenState createState() => _HistoryScreenState();
}
class _HistoryScreenState extends State<HistoryScreen> {
#override
Widget build(BuildContext context) {
//List<Meds> temp1 = context.watch<MedicinesNotifier>().history;
return Scaffold(
bottomNavigationBar: BottomBar(),
body: SafeArea(
child: Column(
children: <Widget>[
Container(
color: Theme.of(context).primaryColor,
child: Padding(
padding: EdgeInsets.fromLTRB(15.0, 0.0, 15.0, 30.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
TextButton(
onPressed: () {},
child: Text(
"Filter",
style: TextStyle(
color: Colors.white,
),
),
),
],
),
],
),
),
),
Expanded(
child: Container(
//color: Colors.white54,
decoration: BoxDecoration(
color: Colors.white30,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30.0),
topRight: Radius.circular(30.0),
),
),
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30.0),
topRight: Radius.circular(30.0),
),
child: Consumer<SearchHistoryData?>(**//Using Consumer here**
builder: (context, searchHistoryData, child) {
//assert(child != null);
return ListView.builder(
itemCount: searchHistoryData!.sizeOfHistory(),
itemBuilder: (BuildContext context, int index) {
//final historyData =
return Container(
margin: EdgeInsets.only(...
Exception caught
The following ProviderNotFoundException was thrown building Consumer<SearchHistoryData?>(dirty):
Error: Could not find the correct Provider<SearchHistoryData?> above this Consumer<SearchHistoryData?> Widget
This happens because you used a BuildContext that does not include the provider
of your choice. There are a few common scenarios:
You added a new provider in your main.dart and performed a hot-reload.
To fix, perform a hot-restart.
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that Consumer<SearchHistoryData?> is under your MultiProvider/Provider<SearchHistoryData?>.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
consider using builder like so:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}

Flutter - Chat Screen built with a StreamBuilder showing messages multiple times

I am struggling with this Chat Screen. The app is meant to ask questions (not part of the below code) and the user either selects answers or types them. When the user types a first answer everything goes according to the plan and a first message is displayed. However the app then goes on displaying the second answer twice, the third one three times and so on.
I have been facing this issue for a few days and I cannot figure out why the app behaves the way it does. Could you please take a look at the code and suggest a way to fix this?
To give you some background information, this Chat Screen is part of a larger application. It should subscribe to a stream when the user opens the app. Then each message is pushed to the stream, whether it is a question asked by the bot or an answer given by the User. The system listens to the stream and displays a new message each time the stream broadcasts something, in our case the latest user input.
I am using a list of message models built from the stream to display the messages. For the purpose of asking this question I simplified the model to the extreme but in practice it has 23 fields. Creating this list of messages is the best solution I managed to think of but there may be a better way to handle this situation. Feel free to let me know if you know of any.
Here is the code that I am running.
import 'package:flutter/material.dart';
import 'dart:async';
StreamController<ChatMessageModel> _chatMessagesStreamController = StreamController<ChatMessageModel>.broadcast();
Stream _chatMessagesStream = _chatMessagesStreamController.stream;
const Color primaryColor = Color(0xff6DA7B9);
const Color secondaryColor = Color(0xffF0F0F0);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Chat Screen',
home: ChatScreen(),
);
}
}
class ChatMessageModel {
final String message;
const ChatMessageModel({
this.message,
}
);
factory ChatMessageModel.turnSnapshotIntoListRecord(Map data) {
return ChatMessageModel(
message: data['message'],
);
}
#override
List<Object> get props => [
message,
];
}
class ChatScreen extends StatefulWidget {
static const String id = 'chat_screen9';
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _messageTextController = TextEditingController();
String _userInput;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: secondaryColor,
appBar: AppBar(
title: Row(
children: [
Container(
padding: EdgeInsets.all(8.0),
child: Text('Chat Screen',
style: TextStyle(color: Colors.white,),
),
)
],
),
backgroundColor: primaryColor,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
MessagesStream(),
Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(
color: primaryColor,
width: 1.0,
),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
controller: _messageTextController,
onChanged: (value) {
_userInput = value;
},
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
hintText: 'Type your answer here',
// border: InputBorder.none,
),
),
),
TextButton(
onPressed: () {
_messageTextController.clear();
debugPrint('Adding a ChatMessageModel with the message $_userInput to the Stream');
ChatMessageModel chatMessageModelRecord = ChatMessageModel(message: _userInput);
_chatMessagesStreamController.add(chatMessageModelRecord,);
},
child: Text(
'OK',
style: TextStyle(
color: primaryColor,
fontWeight: FontWeight.bold,
fontSize: 18.0,
),
),
),
],
),
),
],
),
),
);
}
}
class MessagesStream extends StatelessWidget {
List<ChatMessageModel> _allMessagesContainedInTheStream = [];
#override
Widget build(BuildContext context) {
return StreamBuilder<ChatMessageModel>(
stream: _chatMessagesStream,
builder: (context, snapshot) {
_chatMessagesStream.listen((streamedMessages) {
// _allMessagesContainedInTheStream.clear();
debugPrint('Value from controller: $streamedMessages');
_allMessagesContainedInTheStream.add(streamedMessages);
}
);
return Expanded(
child: ListView.builder(
// reverse: true,
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
itemCount: _allMessagesContainedInTheStream.length,
itemBuilder: (BuildContext context, int index) {
if (snapshot.hasData) {
return UserChatBubble(chatMessageModelRecord: _allMessagesContainedInTheStream[index]);
}
},
),
);
},
);
}
}
class UserChatBubble extends StatelessWidget {
final ChatMessageModel chatMessageModelRecord;
const UserChatBubble({
Key key,
#required this.chatMessageModelRecord,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: EdgeInsets.symmetric(vertical: 5, horizontal: 5,),
child: Container(
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 7 / 10,),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(15.0),
bottomRight: Radius.circular(15.0),
topLeft: Radius.circular(15.0),
),
color: primaryColor,
),
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 20,),
child: Text(chatMessageModelRecord.message,
style: TextStyle(
fontSize: 17,
// fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
),
],
);
}
}
First of all, thank you for the interesting problem and functioning example provided. I had to do some small changes to convert it to "null-safety", but my code should work on your computer too.
The only problem you had initialization of _chatMessagesStream listener. You should do it only once and ideally in initState, to call it only once.
So here is the fix for you:
class MessagesStream extends StatefulWidget {
#override
_MessagesStreamState createState() => _MessagesStreamState();
}
class _MessagesStreamState extends State<MessagesStream> {
final List<ChatMessageModel> _allMessagesContainedInTheStream = [];
#override
void initState() {
_chatMessagesStream.listen((streamedMessages) {
// _allMessagesContainedInTheStream.clear();
debugPrint('Value from controller: $streamedMessages');
_allMessagesContainedInTheStream.add(streamedMessages);
});
super.initState();
}
#override
Widget build(BuildContext context) {
return StreamBuilder<ChatMessageModel>(
stream: _chatMessagesStream,
builder: (context, snapshot) {
return Expanded(
child: ListView.builder(
// reverse: true,
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
itemCount: _allMessagesContainedInTheStream.length,
itemBuilder: (BuildContext context, int index) {
if (snapshot.hasData) {
return UserChatBubble(
chatMessageModelRecord:
_allMessagesContainedInTheStream[index],
);
} else {
print(snapshot.connectionState);
return Container();
}
},
),
);
},
);
}
}
Also providing full code for null-safety just in case!
import 'package:flutter/material.dart';
import 'dart:async';
final StreamController<ChatMessageModel> _chatMessagesStreamController =
StreamController<ChatMessageModel>.broadcast();
final Stream<ChatMessageModel> _chatMessagesStream =
_chatMessagesStreamController.stream;
const Color primaryColor = Color(0xff6DA7B9);
const Color secondaryColor = Color(0xffF0F0F0);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Chat Screen',
home: ChatScreen(),
);
}
}
class ChatMessageModel {
final String? message;
const ChatMessageModel({
this.message,
});
factory ChatMessageModel.turnSnapshotIntoListRecord(Map data) {
return ChatMessageModel(
message: data['message'],
);
}
List<Object> get props => [
message!,
];
}
class ChatScreen extends StatefulWidget {
static const String id = 'chat_screen9';
#override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _messageTextController = TextEditingController();
String? _userInput;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: secondaryColor,
appBar: AppBar(
title: Row(
children: [
Container(
padding: EdgeInsets.all(8.0),
child: Text(
'Chat Screen',
style: TextStyle(
color: Colors.white,
),
),
)
],
),
backgroundColor: primaryColor,
),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
MessagesStream(),
Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(
color: primaryColor,
width: 1.0,
),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
controller: _messageTextController,
onChanged: (value) {
_userInput = value;
},
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
vertical: 10.0, horizontal: 20.0),
hintText: 'Type your answer here',
// border: InputBorder.none,
),
),
),
TextButton(
onPressed: () {
_messageTextController.clear();
debugPrint(
'Adding a ChatMessageModel with the message $_userInput to the Stream');
ChatMessageModel chatMessageModelRecord =
ChatMessageModel(message: _userInput);
_chatMessagesStreamController.add(
chatMessageModelRecord,
);
},
child: Text(
'OK',
style: TextStyle(
color: primaryColor,
fontWeight: FontWeight.bold,
fontSize: 18.0,
),
),
),
],
),
),
],
),
),
);
}
}
class MessagesStream extends StatefulWidget {
#override
_MessagesStreamState createState() => _MessagesStreamState();
}
class _MessagesStreamState extends State<MessagesStream> {
final List<ChatMessageModel> _allMessagesContainedInTheStream = [];
#override
void initState() {
_chatMessagesStream.listen((streamedMessages) {
// _allMessagesContainedInTheStream.clear();
debugPrint('Value from controller: $streamedMessages');
_allMessagesContainedInTheStream.add(streamedMessages);
});
super.initState();
}
#override
Widget build(BuildContext context) {
return StreamBuilder<ChatMessageModel>(
stream: _chatMessagesStream,
builder: (context, snapshot) {
return Expanded(
child: ListView.builder(
// reverse: true,
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
itemCount: _allMessagesContainedInTheStream.length,
itemBuilder: (BuildContext context, int index) {
if (snapshot.hasData) {
return UserChatBubble(
chatMessageModelRecord:
_allMessagesContainedInTheStream[index],
);
} else {
print(snapshot.connectionState);
return Container();
}
},
),
);
},
);
}
}
class UserChatBubble extends StatelessWidget {
final ChatMessageModel chatMessageModelRecord;
const UserChatBubble({
Key? key,
required this.chatMessageModelRecord,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: EdgeInsets.symmetric(
vertical: 5,
horizontal: 5,
),
child: Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 7 / 10,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(15.0),
bottomRight: Radius.circular(15.0),
topLeft: Radius.circular(15.0),
),
color: primaryColor,
),
padding: EdgeInsets.symmetric(
vertical: 8,
horizontal: 20,
),
child: Text(
"${chatMessageModelRecord.message}",
style: TextStyle(
fontSize: 17,
// fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
),
],
);
}
}

Sending photo to GridView from preview page

When the user agrees to the photo, I want to send the photo back to the homepage where they can access the photo later on.
Currently, I am just opening the camera again on the PhotoPreview page when the user clicks the second button (OutlineButton). Instead, I want this photo to be sent to the homepage.
Here is the relevant portion of the PhotoPreview page
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.pop(
context), // Go back to the camera to take the picture again
child: Icon(Icons.camera_alt),
),
appBar: AppBar(title: Text('Photo Preview')),
body: Column(children: [
Expanded(child: Image.file(File(widget.imagePath))),
const SizedBox(height: 16.0),
OutlineButton(
onPressed: () {
_openGallery();
Navigator.pop(context);
},
child: Text('Okay'),
borderSide: BorderSide(color: Color(0xff33333D)),
),
]),
);
}
}
The Gridview on my home page, which renders the photo in the format I want, is as such
: GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
crossAxisSpacing: 25,
mainAxisSpacing: 25,
childAspectRatio: (80 / 150),
padding: const EdgeInsets.all(2.0),
children:
List.generate(widget.imageArray.length, (index) {
return Container(
decoration: new BoxDecoration(
color: const Color(0xff000000),
borderRadius: BorderRadius.circular(10),
image: new DecorationImage(
image: FileImage(widget.imageArray[index]),
fit: BoxFit.fill,
colorFilter: new ColorFilter.mode(
Colors.black.withOpacity(0.4),
BlendMode.dstATop),
How can I connect the two, for when the user clicks the OutlineButton that it sends the photo on the preview page to the home screen in the format above?
Edit per answer: Here is full Homepage
import 'dart:io';
import 'package:flutter/material.dart';
class Homepage_1 extends StatefulWidget {
final List<File> imageArray;
Homepage_1({Key key, this.imageArray}) : super(key: key);
#override
_Homepage_1State createState() => _Homepage_1State();
}
class _Homepage_1State extends State<Homepage_1> {
var image;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.white,
body: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
Padding(
padding:
const EdgeInsets.only(top: 100, left: 40, right: 0, bottom: 0),
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(
'App Name',
style: TextStyle(
fontSize: 60,
fontFamily: 'Avenir',
fontWeight: FontWeight.w900,
),
),
Container(
margin:
EdgeInsets.only(top: 0, left: 0, right: 50, bottom: 0),
child: widget.imageArray.isEmpty
? Column(children: [
Text(
'Yikes! You have no photos',
style: TextStyle(
fontSize: 19,
fontFamily: 'Avenir',
fontWeight: FontWeight.w900,
),
),
Text(
'Click the circular button below'
style: TextStyle(
fontSize: 15,
fontFamily: 'Avenir',
fontWeight: FontWeight.w500,
),
),
])
: GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
crossAxisSpacing: 25,
mainAxisSpacing: 25,
childAspectRatio: (80 / 150),
padding: const EdgeInsets.all(2.0),
children:
List.generate(widget.imageArray.length, (index) {
return Container(
decoration: new BoxDecoration(
color: const Color(0xff000000),
borderRadius: BorderRadius.circular(10),
image: new DecorationImage(
image: FileImage(widget.imageArray[index]),
fit: BoxFit.fill,
colorFilter: new ColorFilter.mode(
Colors.black.withOpacity(0.4),
BlendMode.dstATop),
),
),
);
})))
]),
)
]));
}
}
& here is full Photo preview screen:
import 'package:flutter/material.dart';
import 'dart:io';
class PhotoPreviewScreen extends StatefulWidget {
Function setData;
final String imagePath;
PhotoPreviewScreen({Key key, this.setData, this.imagePath}) : super(key: key);
_PhotoPreviewScreenState createState() => _PhotoPreviewScreenState();
}
class _PhotoPreviewScreenState extends State<PhotoPreviewScreen> {
var image;
Future _openGallery() async {
if (widget.setData != null) {
widget.setData(File(image.path));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.pop(
context), // Go back to the camera to take the picture again
child: Icon(Icons.camera_alt),
),
appBar: AppBar(title: Text('Photo Preview')),
body: Column(children: [
Expanded(child: Image.file(File(widget.imagePath))),
const SizedBox(height: 16.0),
OutlineButton(
onPressed: () async {
await _openGallery();
Navigator.of(context).pop(widget.imagePath);
},
child: Text('Okay'),
borderSide: BorderSide(color: Color(0xff33333D)),
),
]),
);
}
}
You can pass arguments to pop method and received that as a return value of push .
I wrote a minimal sample for you. Hopefully you get the idea, but if you have any questions, please don't hesitate to ask!
class PhotoPreviewPage extends StatefulWidget {
const PhotoPreviewPage({Key? key, #required this.imagePath})
: super(key: key);
#override
_PhotoPreviewPageState createState() => _PhotoPreviewPageState();
final String imagePath;
}
class _PhotoPreviewPageState extends State<PhotoPreviewPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: OutlineButton(
onPressed: () {
Navigator.of(context).pop(widget.imagePath);
},
child: const Text('OK'),
),
),
);
}
}
/// This is a overly simplified version of the CameraPage
/// Basically, you take a photo and pass that to cameraPreview page
class CameraPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FlatButton(
onPressed: () async {
final imagePath = await _takeAPhoto();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => PhotoPreviewPage(imagePath: imagePath),
),
);
},
child: Text('take a photo'),
),
),
);
}
Future<String> _takeAPhoto() {
// some logic to take a photo and return imagePath
return imagePath;
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
final List<File> imageArray = [];
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: GridView.count(
crossAxisCount: 2,
children: List.generate(
widget.imageArray.length,
(index) => Container(
decoration: BoxDecoration(
image: DecorationImage(
image: FileImage(widget.imageArray[index]),
fit: BoxFit.fill,
),
),
),
),
),
bottomNavigationBar: Row(
children: [
IconButton(
onPressed: () async {
final filePath = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => PhotoPreviewPage(),
),
);
if (filePath != null) {
setState(() {
widget.imageArray.add(File(filePath));
});
}
},
icon: Icon(Icons.camera),
),
],
),
);
}
}
But depending on the exact page structure of your app, you might need to look into state management solutions like Bloc or Riverpod.

How do I navigator.push a showModalBottomSheet for flutter?

How do I navigator.push a showModalBottomSheet for flutter?
class SetRepeatButton extends StatefulWidget {
#override
_SetRepeatButtonState createState() => _SetRepeatButtonState();
}
class _SetRepeatButtonState extends State<SetRepeatButton> {
void _repeatMenu(BuildContext context) {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
backgroundColor: Colors.white,
context: context,
builder: (builder) => Padding(
padding: const EdgeInsets.all(20.0),
child: Container(
height: MediaQuery.of(context).copyWith().size.height / 3,
child: Column(
children: <Widget>[
],
),
),
)
);
}
#override
Widget build(BuildContext context) {
return Builder(
builder: (builder) => FlatButton(
color: Colors.white,
textColor: Colors.grey,
disabledColor: Colors.grey,
disabledTextColor: Colors.black,
onPressed: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => _repeatMenu()),
);
},
child: Row(
),
),
);
}
}
what do you want to achieve?
if just to show the bottomsheet, just call the function in on press.
but it will be a little different if you want to catch the variable passed from the bottomsheet to the main context
you can try like this to show bottomsheet
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: SetRepeatButton(),
);
}
}
class SetRepeatButton extends StatefulWidget {
#override
_SetRepeatButtonState createState() => _SetRepeatButtonState();
}
class _SetRepeatButtonState extends State<SetRepeatButton> {
void _repeatMenu(BuildContext context) {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
backgroundColor: Colors.white,
context: context,
builder: (builder) => Padding(
padding: const EdgeInsets.all(20.0),
child: Container(
height: MediaQuery.of(context).copyWith().size.height / 3,
child: Column(
children: <Widget>[
Text('Text'),
],
),
),
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Text'),
),
body: Center(
child: RaisedButton(
color: Colors.white,
textColor: Colors.grey,
disabledColor: Colors.grey,
disabledTextColor: Colors.black,
onPressed: () async {
_repeatMenu(context);
},
child: Text('Text'),
),
),
);
}
}