I have a code that outputs fields for the user to fill in (code below. I have shortened it here for ease of reading.). I would like to add one more field to this form, which can upload various photos from the phone gallery (preferably with the ability to delete a photo if the user made a mistake when choosing). How can I implement this?
class FormForDeviceService extends StatefulWidget {
#override
State<StatefulWidget> createState() => _FormForDeviceService();
}
class _FormForDeviceService extends State {
final _formKey = GlobalKey<FormState>();
Widget build(BuildContext context) {
return Container(padding: const EdgeInsets.all(10.0),
child: Form(key: _formKey, child: Column(children: <Widget>[
new Text('What is problem', style: TextStyle(fontSize: 20.0),),
new TextFormField(decoration: const InputDecoration(
hintText: 'Describe the problem',),
ElevatedButton(
onPressed: (){if(_formKey.currentState!.validate()) {_formKey.currentState?.reset();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Form completed successfully', style: TextStyle(color: Colors.black),),
backgroundColor: Colors.yellow,));
}},
child: const Text('Submit', style: TextStyle(color: Colors.black),),
style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.yellow)),)
],)));
}
}
Page at the moment
my expectations (or something similar)
An another approach is here-
(I have made a separate widget to handle all these things and you just need to attach it in any scrollable widget)
my code is as follow:
Main Code with that custom widget:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:image_memory/image_picker_widget.dart';
void main() {
runApp(GetMaterialApp(title: 'Flutter', home: Flutter()));
}
class Flutter extends StatefulWidget {
const Flutter({Key? key}) : super(key: key);
#override
State<Flutter> createState() => _FlutterState();
}
class _FlutterState extends State<Flutter> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter'),
centerTitle: true,
),
body: Center(
child: Column(
children: [
//This is the widget I am talking about
ImagePickerWidget()
],
),
),
);
}
}
And now the code for that custom widget:
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
class ImagePickerWidget extends StatefulWidget {
const ImagePickerWidget({Key? key}) : super(key: key);
#override
State<ImagePickerWidget> createState() => _ImagePickerWidgetState();
}
class _ImagePickerWidgetState extends State<ImagePickerWidget> {
late List<CustomImage> images;
late double size;
late ImagePicker imagePicker;
late int idGenerator;
#override
void initState() {
images = [];
size = 100;
idGenerator = 0;
imagePicker = ImagePicker();
}
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
pickImage();
},
child: Text('Pick Image')),
Wrap(
children: images.map((image) {
return Stack(children: [
SizedBox(
height: size,
width: size,
child: ClipRRect(
child: Image.memory(
image.imageData,
fit: BoxFit.fill,
))),
Positioned(
right: 4,
top: 4,
child: InkWell(
onTap: () {
//delete image
images.removeWhere(
(element) => element.imageData == image.imageData);
setState(() {});
},
child: Container(
color: Colors.white, child: Icon(Icons.clear))))
]);
}).toList())
],
);
}
Future<void> pickImage() async {
// XFile? image = await imagePicker.pickImage(source: ImageSource.camera);
XFile? image = await imagePicker.pickImage(source: ImageSource.gallery);
if (image != null) {
Uint8List imageData = await image.readAsBytes();
int id = idGenerator++;
images.add(CustomImage(imageData: imageData, id: id));
setState(() {});
}
}
}
class CustomImage {
Uint8List imageData;
int id;
CustomImage({required this.imageData, required this.id});
}
You can customize the widget in order to use the images list of that widget or you can simply pass the callbacks for that.
we store file here your can use path(string) instead file
List<File> myfile = [];
image_picker package used here to pick image
image_picker: ^0.8.4+10
call like this in your code
Container(
height: 200,
padding: EdgeInsets.all(4),
child: PickPhoto())
Pick photo widget
class PickPhoto extends StatefulWidget {
const PickPhoto({Key? key}) : super(key: key);
#override
State<PickPhoto> createState() => _PickPhotoState();
}
class _PickPhotoState extends State<PickPhoto> {
#override
Widget build(BuildContext context) {
return Material(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
width: 45,
height: 45,
child: ElevatedButton(
onPressed: () async {
var file =
await picker?.pickImage(source: ImageSource.gallery);
setState(() {
myfile.add(File(file!.path));
});
},
child: Text("Add Photo"))),
),
Expanded(
child: ListView.builder(
// physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: myfile.length,
itemBuilder: (context, index) => Container(
padding: EdgeInsets.all(4),
height: 175,
width: 125,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Align(
alignment: Alignment.topRight,
child: IconButton(
onPressed: () {
setState(() {
myfile.removeAt(index);
});
},
icon: Icon(Icons.close),
),
),
Expanded(
child: Container(
child: myfile[index] == null
? Text("")
: Image.file(
myfile[index],
fit: BoxFit.fill,
),
),
),
],
),
)),
)
],
),
);
}
}
SampleCode
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
ImagePicker? picker;
void main() {
WidgetsFlutterBinding.ensureInitialized();
picker = ImagePicker();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MySQL Test',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [FormForDeviceService()],
),
);
}
}
List<File> myfile = [];
List<int> f = [1, 2, 3, 4, 5];
List<bool> fs = [false, false, false, true, true];
class FormForDeviceService extends StatefulWidget {
#override
State<StatefulWidget> createState() => _FormForDeviceService();
}
class _FormForDeviceService extends State {
final _formKey = GlobalKey<FormState>();
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(10.0),
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
new Text(
'What is problem',
style: TextStyle(fontSize: 20.0),
),
new TextFormField(
decoration: const InputDecoration(
hintText: 'Describe the problem',
),
),
Container(
height: 200,
padding: EdgeInsets.all(4),
child: PickPhoto()),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState?.reset();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text(
'Form completed successfully',
style: TextStyle(color: Colors.black),
),
backgroundColor: Colors.yellow,
));
}
},
child: const Text(
'Submit',
style: TextStyle(color: Colors.black),
),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.yellow)),
)
],
)));
}
}
class PickPhoto extends StatefulWidget {
const PickPhoto({Key? key}) : super(key: key);
#override
State<PickPhoto> createState() => _PickPhotoState();
}
class _PickPhotoState extends State<PickPhoto> {
#override
Widget build(BuildContext context) {
return Material(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
width: 45,
height: 45,
child: ElevatedButton(
onPressed: () async {
var file =
await picker?.pickImage(source: ImageSource.gallery);
setState(() {
myfile.add(File(file!.path));
});
},
child: Text("Add Photo"))),
),
Expanded(
child: ListView.builder(
// physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: myfile.length,
itemBuilder: (context, index) => Container(
padding: EdgeInsets.all(4),
height: 175,
width: 125,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Align(
alignment: Alignment.topRight,
child: IconButton(
onPressed: () {
setState(() {
myfile.removeAt(index);
});
},
icon: Icon(Icons.close),
),
),
Expanded(
child: Container(
child: myfile[index] == null
? Text("")
: Image.file(
myfile[index],
fit: BoxFit.fill,
),
),
),
],
),
)),
)
],
),
);
}
}
Related
I have a Stateful widget that i pass a list to (for example 2 items).
After I delete an item, the widget should rebuild itself.
Unfortunately, the deleted item is still displayed and the other one is not.
When I re-enter the widget, the correct item is loaded.
There is a similar problem List not updating on deleting item
but maybe someone can explain me what i did wrong and why provider is helping me here instead of setState?
My code is:
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:trip_planner/util/dialog_box.dart';
import 'package:trip_planner/util/previewUrl.dart';
class BookingPage extends StatefulWidget {
final List toDoList;
BookingPage({
super.key,
required this.toDoList,
});
#override
State<BookingPage> createState() => _BookingPageState();
}
class _BookingPageState extends State<BookingPage> {
//text controller
final _controller = TextEditingController();
final _database = FirebaseDatabase.instance.ref();
//Liste is an example what i have in my list
List toDoList2 = [
["https://www.booking.com/Share-Rnv2Kf", true],
["https://www.booking.com/Share-3hKQ0r", true],
];
void initState(){
super.initState();
}
void deleteTask(int index){
setState(() {
widget.toDoList.removeAt(index);
});
//DatabaseReference _testRef = _database.child("Hotel:");
//_testRef.set(widget.toDoList.toString());
}
//save new Item
void saveNewItem(){
setState(() {
widget.toDoList.add([_controller.text, false]);
//DatabaseReference _testRef = _database.child("Hotel:");
//_testRef.set(widget.toDoList.toString());
_controller.clear();
});
Navigator.of(context).pop();
}
void createNewItem(){
showDialog(
context: context,
builder: (context){
return DialogBox(
controller: _controller,
onSave: saveNewItem,
onCancel: () => Navigator.of(context).pop(),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Booking Seiten'),
elevation: 0,
),
floatingActionButton: FloatingActionButton(
onPressed: createNewItem,
child: Icon(Icons.add),
),
body: ListView.builder(
itemCount: widget.toDoList.length,
itemBuilder: (context, index){
return PreviewUrl(
url2: widget.toDoList[index][0],
deleteFunction: (context) => setState(() => deleteTask(index)),
);
},
),
);
}
}
i thought setState does the same thing as when i re-enter the widget, but it doesn't.
import 'package:any_link_preview/any_link_preview.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:url_launcher/url_launcher.dart';
class PreviewUrl extends StatelessWidget {
final String url2;
//Function(bool?)? onChanged;
Function(BuildContext)? deleteFunction;
PreviewUrl({
super.key,
required this.url2,
required this.deleteFunction,
//required this.onChanged,
});
Future openBrowserURL({
required String url,
bool inApp = false,
}) async {
if(await canLaunch(url)){
await launch(
url,
forceSafariVC: inApp, //iOS
forceWebView: inApp, //Android
enableJavaScript: true, //Android
);
}
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(1.0),
child: Slidable(
endActionPane: ActionPane(
motion: StretchMotion(),
children: [
SlidableAction(
onPressed: deleteFunction,
icon: Icons.delete,
backgroundColor: Colors.red.shade300,
borderRadius: BorderRadius.circular(12),
)
],
),
child: Container(
child: AnyLinkPreview.builder(
link: url2,
itemBuilder: (context, metadata, imageProvider) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (imageProvider != null)
GestureDetector(
onTap: () async {
final url = url2;
openBrowserURL(url: url, inApp: true);
},
child: Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.width *0.25,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12)),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
),
Container(
width: double.infinity,
color: Theme.of(context).primaryColor.withOpacity(0.6),
padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (metadata.title != null)
Text(
metadata.title!,
maxLines: 1,
style:
const TextStyle(fontWeight: FontWeight.w500),
),
const SizedBox(height: 5),
if (metadata.desc != null)
Text(
metadata.desc!,
maxLines: 1,
style: Theme.of(context).textTheme.bodySmall,
),
Text(
metadata.url ?? url2,
maxLines: 1,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
],
),
),
),
),
);
}
}
If you run the simplified version of your code in DartPad - it will work:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
List toDoList = [
["Button 1", true],
["Button 2", true],
];
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: BookingPage(toDoList: toDoList),
),
),
);
}
}
class BookingPage extends StatefulWidget {
final List toDoList;
const BookingPage({
super.key,
required this.toDoList,
});
#override
State<BookingPage> createState() => _BookingPageState();
}
class _BookingPageState extends State<BookingPage> {
//Liste is an example what i have in my list
List toDoList2 = [
["Button 1", true],
["Button 2", true],
];
#override
void initState() {
super.initState();
}
void deleteTask(int index) {
setState(() {
widget.toDoList.removeAt(index);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Booking Seiten'),
elevation: 0,
),
body: ListView.builder(
itemCount: widget.toDoList.length,
itemBuilder: (context, index) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.lightBlue,
padding: const EdgeInsets.all(12),
textStyle: const TextStyle(fontSize: 22),
),
child: Text(widget.toDoList[index][0]!),
onPressed: () => setState(() => deleteTask(index)),
);
},
),
);
}
}
Which tells me that the problem is your PreviewUrl. My guess is - it is a statful widget, and when the tree rebuilds - it will link the old State object to the first item.
Using Keys might help, something like:
return PreviewUrl(
key: ObjectKey(widget.toDoList[index]),
url2: widget.toDoList[index][0],
deleteFunction: (context) => setState(() => deleteTask(index)),
);
I'm developing a simple blog for a uni project and unsure how to proceed, i've followed some tutorials to get to where I am and now a bit stuck.
I currently have the home screen with an 'add'(camera icon) button which can be seen in image one, I then have a second screen (image 2) which allows user to input name, title and image.
What I want to do is add a save button to the bottom of image 2 which then displays the saved text / image in listview in image 1, and be able to save multiple posts which then stack on top of each other.
The tutorial I was following was using firebase but I would like to do this with JSON, firstly is this possible? and can anyone suggest any methods on how to do this.
code ive written so far:
main.dart
import 'package:blog/views/home.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData.dark(),
home: HomePage(),
);
}
}
create_blog.dart
import 'package:image_picker/image_picker.dart';
import 'dart:io' show File;
import 'package:flutter/material.dart';
class CreateBlog extends StatefulWidget {
const CreateBlog({Key? key}) : super(key: key);
#override
State<CreateBlog> createState() => _CreateBlogState();
}
class _CreateBlogState extends State<CreateBlog> {
File? image2;
final _picker = ImagePicker();
String? authorName, title, desc;
File? selectedImage;
Future getImage() async {
final XFile? image = await _picker.pickImage(source:
ImageSource.gallery);
if (image != null) {
setState(
() {
selectedImage = File(image.path);
},
);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Row(
// Top of Screen Text //
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text(
'Travel',
style: TextStyle(fontSize: 22),
),
Text('Blog', style: TextStyle(fontSize: 22, color:
Colors.green))
],
),
backgroundColor: Colors.transparent,
elevation: 0.0,
// Top of Screen widget //
actions: <Widget>[
GestureDetector(
// Upload Button //
onTap: () {},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
child: const Icon(
Icons.upload_file,
),
),
),
],
),
// Next Widget //
body: Container(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Column(
// Get Image Button //
children: <Widget>[
const SizedBox(height: 10),
GestureDetector(
onTap: () {
getImage();
},
// Image Return //
child: selectedImage != null
? Container(
margin: const EdgeInsets.symmetric(horizontal:
16),
height: 150,
width: MediaQuery.of(context).size.width,
child: ClipRRect(
borderRadius: BorderRadius.circular(6),
child:
Image.file(selectedImage!, fit: BoxFit.cover)),
)
// Hold Image Box //
: Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
height: 150,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6)),
width: MediaQuery.of(context).size.width,
child: const Icon(Icons.add_a_photo,
color: Colors.black))),
// Text Boxes Code //
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
children: <Widget>[
TextField(
decoration: const InputDecoration(hintText: 'Title'),
onChanged: (val) {
authorName = val;
},
),
TextField(
decoration: const InputDecoration(hintText: 'Date'),
onChanged: (val) {
title = val;
},
),
TextField(
autofocus: true,
maxLines: null,
decoration: const InputDecoration(hintText: 'Blog Body'),
onChanged: (val) {
desc = val;
},
),
],
),
)
],
),
),
);
}
}
home.dart
import 'package:flutter/material.dart';
import 'create_blog.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
// Main Home Page //
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text(
'Travel',
style: TextStyle(fontSize: 22),
),
Text('Blog',
style: TextStyle(fontSize: 22, color: Colors.green))
]),
backgroundColor: Colors.transparent,
elevation: 0.0,
),
body: Container(),
floatingActionButton: Container(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CreateBlog()));
},
child: const Icon(Icons.add_a_photo))
]),
));
}
}
So far the code works great and I am able to upload an image to the second screen from local storage, just unsure how to save it.
I started writing some JSON code from another tutorial but unsure if this is the right way to go
client.dart
import 'dart:io';
import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart';
class FileManager {
static FileManager? _instance;
FileManager._internal() {
_instance = this;
}
factory FileManager() => _instance ?? FileManager._internal();
Future<String> get _directoryPath async {
Directory? directory = await getExternalStorageDirectory();
return directory!.path;
}
Future<File> get _file async {
final path = await _directoryPath;
return File('$path/blog.txt');
}
Future<String> readTextFile() async {
String fileContent = 'blog text';
File file = await _file;
if (await file.exists()) {
try {
fileContent = await file.readAsString();
} catch (e) {
print(e);
}
}
return fileContent;
}
}
Sorry about length of post and thanks in advance!
I will recommend this course: Udemy: Flutter. You need to understand the basics of flutter.
I'm quite inexperienced with flutter and have created this script.
When you tap on the red container you create a Row of buttons,
I would like when I click on a button in the Row -> the text of the blue container becomes the same as the text contained in the tapped button
Anyone know how I can do?
Thank you :)
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
void main() => runApp(mainApp());
class mainApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: Chat(),
);
}
}
class Chat extends StatefulWidget {
const Chat({Key? key}) : super(key: key);
#override
_ChatState createState() => _ChatState();
}
class _ChatState extends State<Chat> {
String text = 'Henlo i am Gabriele!';
List<Container> OutputList = [];
void tool(String text) async {
List ListText = text.split(' ');
for (var i in ListText) {
OutputList.add(
Container(
child: GestureDetector(
onTap: () {},
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Container(
color: Colors.orange,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(i),
),
),
),
),
),
);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
GestureDetector(
onTap: () {
setState(() {
tool(text);
print(OutputList);
});
},
child: Container(
width: 150.0,
height: 50.0,
color: Colors.red,
child: Center(child: Text('START ->')),
),
),
SizedBox(height: 50.0),
Row(
children: OutputList,
),
SizedBox(height: 50.0),
Container(
color: Colors.blue,
width: 200.0,
height: 50.0,
child: Text(''),
),
],
),
),
);
}
}
Yes you can add a few line of code check here i try to solve.
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
void main() => runApp(mainApp());
class mainApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: Chat(),
);
}
}
class Chat extends StatefulWidget {
const Chat({Key? key}) : super(key: key);
#override
_ChatState createState() => _ChatState();
}
class _ChatState extends State<Chat> {
String text = 'Henlo i am Gabriele!';
//step 1 create variable
String newGeneratedText = "";
List<Container> OutputList = [];
void tool(String text) async {
List ListText = text.split(' ');
for (var i in ListText) {
OutputList.add(
Container(
child: GestureDetector(
onTap: () {
//add logic here to concatinate values
setState(() {
newGeneratedText = newGeneratedText + " " + i;//added " " for one space
});
},
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Container(
color: Colors.orange,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(i),
),
),
),
),
),
);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
GestureDetector(
onTap: () {
setState(() {
tool(text);
print(OutputList);
});
},
child: Container(
width: 150.0,
height: 50.0,
color: Colors.red,
child: Center(child: Text('START ->')),
),
),
SizedBox(height: 50.0),
Wrap( // added for fixing more values and solve overflow exceptions error
children: OutputList,
),
SizedBox(height: 50.0),
Container(
color: Colors.blue,
width: 200.0,
height: 50.0,
child: Text(newGeneratedText), //final print values
),
],
),
),
);
}
}
I'm trying to check if the keyboard is visible after tapping on the TextFormField by calling:
if (MediaQuery.of(context).viewInsets.bottom != 0) {
...
}
but as soon as I have this MediaQuery call in my code, the Keyboard doesn't even open anymore after tapping on the TextFormField...
Edited:
This is what happens when tapping on the TextFormField:
I added the code of the page which causes this faulty behavior:
class LearnPage extends StatefulWidget {
final int topicId;
final String topicName;
LearnPage(this.topicId, this.topicName);
#override
_LearnPageState createState() => _LearnPageState();
}
class _LearnPageState extends State<LearnPage> {
final mainCaardIndex = ValueNotifier<int>(0);
PageController _mainCaardController;
PageController _inputCaardController;
List<CaardM> caards;
List<PageM> mainCaardList = [];
List<List<PageM>> inputCaardList = [];
List<List<TextEditingController>> textControllers = [];
Future<void> async_init() async {
List<CaardM> caardList =
await DatabaseProviderCaard.db.getCaards(widget.topicId);
caards = caardList;
setState(() {});
}
bool _keyboardIsVisible() {
return !(MediaQuery.of(context).viewInsets.bottom == 0.0);
}
#override
void initState() {
async_init();
_mainCaardController = PageController();
_inputCaardController = PageController();
super.initState();
}
#override
void dispose() {
_mainCaardController.dispose();
_inputCaardController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.lightBlue,
title: Center(
child: Text(
widget.topicName,
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold),
),
),
actions: [
!_keyboardIsVisible()
? IconButton(
icon: Icon(Icons.check_circle_outline),
tooltip: 'Validate',
onPressed: validate,
)
: IconButton(
icon: Icon(Icons.keyboard_hide),
onPressed: () {
FocusManager.instance.primaryFocus.unfocus();
},
),
],
),
body: Column(
children: [
Expanded(
flex: 3,
child: FutureBuilder(
future: getMainContent(),
builder: (context, AsyncSnapshot<int> snapshotMain) {
if (snapshotMain.connectionState == ConnectionState.done) {
return PageView.builder(
itemCount: snapshotMain.data,
controller: _mainCaardController,
onPageChanged: (position) {
mainCaardIndex.value = position;
mainCaardIndex.notifyListeners();
_inputCaardController.jumpToPage(0);
},
itemBuilder: (context, position) {
return LearnMainCaard(
mainCaardList[position].title,
mainCaardList[position].content,
);
},
);
} else {
return CircularProgressIndicator();
}
},
),
),
Expanded(
flex: 5,
child: FutureBuilder(
future: getInputContent(),
builder: (context, AsyncSnapshot<int> snapshotInput) {
if (snapshotInput.connectionState == ConnectionState.done) {
return ValueListenableBuilder(
valueListenable: mainCaardIndex,
builder: (context, value, _) {
return PageView.builder(
itemCount: snapshotInput.data,
controller: _inputCaardController,
itemBuilder: (context, position) {
return LearnInputCaard(
inputCaardList[mainCaardIndex.value][position].title,
textControllers[mainCaardIndex.value][position],
);
},
);
},
);
} else {
return CircularProgressIndicator();
}
},
),
),
],
),
);
}
Future<int> getMainContent() async {
List<PageM> caardPages;
mainCaardList.clear();
for (var i = 0; i < caards.length; i++) {
caardPages = await DatabaseProviderPage.db.getPages(caards[i].id);
if (caards[i].pageAmount > 1) {
mainCaardList.add(caardPages[0]);
}
}
return mainCaardList.length;
}
Future<int> getInputContent() async {
List<PageM> caardPages = [];
List<PageM> list = [];
inputCaardList.clear();
for (var i = 0; i < caards.length; i++) {
caardPages = await DatabaseProviderPage.db.getPages(caards[i].id);
if (caards[i].pageAmount > 1) {
addController(caards[i].pageAmount - 1);
list = [];
for (var i = 1; i < caardPages.length; i++) {
list.add(caardPages[i]);
}
inputCaardList.add(list);
}
}
return inputCaardList[mainCaardIndex.value].length;
}
void addController(int controllerAmount) {
List<TextEditingController> currentTextControllers = [];
print('addController called');
currentTextControllers.clear();
currentTextControllers = List.generate(
controllerAmount, (index) => TextEditingController()
);
textControllers.add(currentTextControllers);
}
And here the LearnInputCaard widget:
import 'package:flutter/material.dart';
class LearnInputCaard extends StatefulWidget {
final String title;
final TextEditingController textController;
LearnInputCaard(
this.title,
this.textController,
);
#override
_LearnInputCaardState createState() => _LearnInputCaardState();
}
class _LearnInputCaardState extends State<LearnInputCaard> {
#override
Widget build(BuildContext context) {
return Container(
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
margin: EdgeInsets.all(20),
color: Colors.amberAccent.shade100,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Expanded(
flex: 1,
child: Text(
widget.title,
style: TextStyle(fontSize: 20),
),
),
Divider(color: Colors.black38,),
Expanded(
flex: 10,
child: Container(
padding: EdgeInsets.all(10.0),
child: TextFormField(
controller: widget.textController,
maxLines: 30,
decoration: InputDecoration(
hintText: "Enter content",
border: InputBorder.none,
),
),
),
)
],
),
),
),
);
}
}
you need to check MediaQuery.of(context).viewInsets.bottom == 0.0
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Keyboard Visibility Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
_keyboardIsVisible()
? Text(
"Keyboard is visible",
style: Theme.of(context)
.textTheme
.display1
.copyWith(color: Colors.blue),
)
: RichText(
text: TextSpan(children: [
TextSpan(
text: "Keyboard is ",
style: Theme.of(context)
.textTheme
.display1
.copyWith(color: Colors.blue),
),
TextSpan(
text: "not ",
style: Theme.of(context)
.textTheme
.display1
.copyWith(color: Colors.red),
),
TextSpan(
text: "visible",
style: Theme.of(context)
.textTheme
.display1
.copyWith(color: Colors.blue),
)
]),
),
SizedBox(
height: 20,
),
Container(
width: 200.0,
child: TextField(
style: Theme.of(context).textTheme.display1,
decoration: InputDecoration(
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blue,
),
borderRadius: BorderRadius.circular(10.0),
),
),
),
)
],
),
));
}
bool _keyboardIsVisible() {
return !(MediaQuery.of(context).viewInsets.bottom == 0.0);
}
}
The problem is that you get the context from the parent widget.
If you call:
MediaQuery.of(context);
in the same widget where your forms are, you shouldn't get this behavior.
You need to define a GlobalKey<FormState> in your highest widget and pass this one down. Then it works. I defined it first in my SafeArea and therefore it failed and I had the same problem with the keyboard.
Here are some snippets of my code. I have a PageController and use two different forms on my two pages.
class OnboardingScaffold extends HookConsumerWidget {
OnboardingScaffold({Key? key}) : super(key: key);
// here you define your GlobalKeys
final _formKeyLogin = GlobalKey<FormState>();
final _formKeyApply = GlobalKey<FormState>();
#override
Widget build(BuildContext context, WidgetRef ref) {
final controller = usePageController();
bool isKeyboard = MediaQuery.of(context).viewInsets.bottom != 0;
return Scaffold(
body: Container(
padding: !isKeyboard
? const EdgeInsets.only(bottom: 80)
: const EdgeInsets.only(bottom: 0),
child: PageView(
controller: controller,
children: [
// here you pass these keys into your child Widget
LoginSafeArea(
formKey: _formKeyLogin,
),
ApplySafeArea(
formKey: _formKeyApply,
),
],
),
),
bottomSheet: !isKeyboard
? Container(height: 80)
: Container(height: 0),
);
}
}
The child Widget should contain a Form Widget:
class LoginSafeArea extends HookConsumerWidget {
const LoginSafeArea({Key? key, required this.formKey}) : super(key: key);
final GlobalKey<FormState> formKey;
#override
Widget build(BuildContext context, WidgetRef ref) {
return SafeArea(
child: Center(
child: Form(
key: formKey,
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.only(left: 24.0, right: 24.0),
child: Column(
children: <Widget>[
const EmailFieldWidget(),
const SizedBox(height: 8.0),
const PasswordFieldWidget(),
const SizedBox(height: 16.0),
LoginButtonWidget(
formKey: formKey,
),
const SizedBox(height: 8.0),
],
),
),
),
),
);
}
}
I am using the package
country_code_picker: ^1.4.0
https://pub.dev/packages/country_code_picker#-installing-tab-
with flutter 1.17.3
Which is pretty much one of the only country code picker packages. But I have one serious problem an I don't have a clue what it could be.
When I run this code
import 'package:flutter/material.dart';
import 'package:country_code_picker/country_code_picker.dart';
void main() {
runApp(App());
}
class App extends StatelessWidget {
App();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: TestWidget(),
);
}
}
class TestWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(body: _buildCountryPicker(context));
}
Widget _buildCountryPicker(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Center(
child: CountryCodePicker(
initialSelection: 'NL',
),
),
);
}
}
And I open the dialog to select a country. I scroll in the list and then select the TextField my keyboard opens and when I try to type something my entire app freezes. I can't even hot reload. I don't get a single error.
I am running this on my Huawei P30, but I also experience this on other android devices. I don't know if this is a flutter bug or a country code picker bug.
I think it is probably in this widget somewhere. If anyone could point me in the right direction it would help me alot!
class SelectionDialog extends StatefulWidget {
final List<CountryCode> elements;
final bool showCountryOnly;
final InputDecoration searchDecoration;
final TextStyle searchStyle;
final TextStyle textStyle;
final WidgetBuilder emptySearchBuilder;
final bool showFlag;
final double flagWidth;
final Size size;
final bool hideSearch;
/// elements passed as favorite
final List<CountryCode> favoriteElements;
SelectionDialog(
this.elements,
this.favoriteElements, {
Key key,
this.showCountryOnly,
this.emptySearchBuilder,
InputDecoration searchDecoration = const InputDecoration(),
this.searchStyle,
this.textStyle,
this.showFlag,
this.flagWidth = 32,
this.size,
this.hideSearch = false,
}) : assert(searchDecoration != null, 'searchDecoration must not be null!'),
this.searchDecoration =
searchDecoration.copyWith(prefixIcon: Icon(Icons.search)),
super(key: key);
#override
State<StatefulWidget> createState() => _SelectionDialogState();
}
class _SelectionDialogState extends State<SelectionDialog> {
/// this is useful for filtering purpose
List<CountryCode> filteredElements;
#override
Widget build(BuildContext context) => SimpleDialog(
titlePadding: const EdgeInsets.all(0),
title: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
IconButton(
padding: const EdgeInsets.all(0),
iconSize: 20,
icon: Icon(
Icons.close,
),
onPressed: () => Navigator.pop(context),
),
if (!widget.hideSearch)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: TextField(
style: widget.searchStyle,
decoration: widget.searchDecoration,
onChanged: _filterElements,
),
),
],
),
children: [
Container(
width: widget.size?.width ?? MediaQuery.of(context).size.width,
height:
widget.size?.height ?? MediaQuery.of(context).size.height * 0.7,
child: ListView(
children: [
widget.favoriteElements.isEmpty
? const DecoratedBox(decoration: BoxDecoration())
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...widget.favoriteElements.map(
(f) => SimpleDialogOption(
child: _buildOption(f),
onPressed: () {
_selectItem(f);
},
),
),
const Divider(),
],
),
if (filteredElements.isEmpty)
_buildEmptySearchWidget(context)
else
...filteredElements.map(
(e) => SimpleDialogOption(
key: Key(e.toLongString()),
child: _buildOption(e),
onPressed: () {
_selectItem(e);
},
),
),
],
),
),
],
);
Widget _buildOption(CountryCode e) {
return Container(
width: 400,
child: Flex(
direction: Axis.horizontal,
children: <Widget>[
if (widget.showFlag)
Flexible(
child: Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Image.asset(
e.flagUri,
package: 'country_code_picker',
width: widget.flagWidth,
),
),
),
Expanded(
flex: 4,
child: Text(
widget.showCountryOnly
? e.toCountryStringOnly()
: e.toLongString(),
overflow: TextOverflow.fade,
style: widget.textStyle,
),
),
],
),
);
}
Widget _buildEmptySearchWidget(BuildContext context) {
if (widget.emptySearchBuilder != null) {
return widget.emptySearchBuilder(context);
}
return Center(
child: Text('No country found'),
);
}
#override
void initState() {
filteredElements = widget.elements;
super.initState();
}
void _filterElements(String s) {
s = s.toUpperCase();
setState(() {
filteredElements = widget.elements
.where((e) =>
e.code.contains(s) ||
e.dialCode.contains(s) ||
e.name.toUpperCase().contains(s))
.toList();
});
}
void _selectItem(CountryCode e) {
Navigator.pop(context, e);
}
}
Also filed an issue on the flutter github https://github.com/flutter/flutter/issues/59886
Edit:
I have a video of it right here
https://www.youtube.com/watch?v=669KitFG9ek&feature=youtu.be
I just had to remove the keys, so there probably was a duplicate key
...filteredElements.map(
(e) => SimpleDialogOption(
//key: Key(e.toLongString()),
child: _buildOption(e),
onPressed: () {
_selectItem(e);
},
),
),