flutter : How to Update OverlayEntry value instead of create new one - flutter

I am trying to use OverlayEntry on onChanged callback from TextField.
body: Center(
child: Container(
child: TextField(
focusNode: _focusNode,
key: _textFieldKey,
style: _textFieldStyle,
onChanged: (String nextText) {
showOverlaidTag(context, nextText);
},
),
width: 400.0,
),
)
The problem is when everytime onChanged is triggered, new Overlay is created and lay on each other? I want to just new text replaced with new text and the old overlay remains?
showOverlaidTag(BuildContext context, String newText) async {
OverlayEntry suggestionTagoverlayEntry = OverlayEntry(
builder: (BuildContext context) => DropDownBody(
focusNode: _focusNode,
newText : newText
),
);
overlayState.insert(suggestionTagoverlayEntry);
//await Future.delayed(Duration(milliseconds: 10000));
suggestionTagoverlayEntry.remove();
}
This is DropDownBody
import 'package:flutter/material.dart';
class DropDownBody extends StatefulWidget {
const DropDownBody({Key? key, required this.focusNode, required this.newText})
: super(key: key);
final FocusNode focusNode;
final String newText;
#override
_DropDownBodyState createState() => _DropDownBodyState();
}
class _DropDownBodyState extends State<DropDownBody> {
#override
Widget build(BuildContext context) {
print("=====> build");
return Positioned(
top: widget.focusNode.offset.dy + 50,
left: 0,
child: Material(
elevation: 4.0,
color: Colors.transparent,
child: Container(
width: MediaQuery.of(context).size.width * 0.9,
color: Colors.lightBlueAccent,
child: Row(
children: [
Expanded(
child: Text(
'Show tag here',
style: TextStyle(
fontSize: 20.0,
),
),
),
],
),
)),
);
}
}

I had to fix a few syntax errors in your code. I implemented the dropdownbody code in part. works great.
class HomePageWidget extends StatefulWidget {
HomePageWidget({Key? key}) : super(key: key);
final formKey=GlobalKey<FormState>();
TextEditingController text_controller= TextEditingController();
#override
_HomePageWidgetState createState() => _HomePageWidgetState();
}
class _HomePageWidgetState extends State<HomePageWidget> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text("test changenotify"),
),
body: Form( key:widget.formKey, child: Column(
children: [
TextFormField(
decoration: InputDecoration(icon: const Icon(Icons.person),
hintText: "Input some text",
labelText: "Input",
),
onChanged: (String nextText){
//debugPrint(nextText);
showOverlaidTag(context,nextText);
//Future<bool> val=showOverlaidTag(context,nextText);
//val.then((result)=>result);
},
controller: widget.text_controller),
],
)));
}
showOverlaidTag(BuildContext context, String newText) async {
OverlayEntry suggestionTagoverlayEntry = OverlayEntry(
builder: (BuildContext context) =>
Positioned(
top:100,
child:
Material(elevation:4.0,
color:Colors.transparent,
child:Container(
width:MediaQuery.of(context).size.width*0.9,
color:Colors.lightBlueAccent,
child:
Row(children: [Expanded(child: Text(
//focusNode: _focusNode,
newText
))],)
),
)));
//overlayState.insert(suggestionTagoverlayEntry);
Overlay.of(context)?.insert(suggestionTagoverlayEntry);
await Future.delayed(Duration(milliseconds: 2000));
suggestionTagoverlayEntry.remove();
return suggestionTagoverlayEntry;
}
}

use boolean and try to call the insert only the first time
if(isOverlayAlreadyRunning == false){
entry = OverlayEntry(builder: (context) {
return Positioned(
);
});
overlay?.insert(entry!);
isOverlayAlreadyRunning = true;
}
and use setState((){}) to update the variable

try this, did a little refactoring for the text, it doesn't change much. Call hideOverlay() when you want to remove it
import 'package:flutter/material.dart';
class DropDownBody extends StatefulWidget {
const DropDownBody({Key? key, required this.focusNode, required this.newText})
: super(key: key);
final FocusNode focusNode;
final String newText;
#override
_DropDownBodyState createState() => _DropDownBodyState();
}
class _DropDownBodyState extends State<DropDownBody> {
OverlayEntry? overlayEntry;
String? text;
void insertOverlay(BuildContext context) {
if (overlayEntry == null || !overlayEntry!.mounted) {
overlayEntry = OverlayEntry(
builder: (context) => DropDownBody(focusNode: _focusNode, newText: text??'sus')
);
final overlay = Overlay.of(context)!;
overlay.insert(overlayEntry!);
}
}
void hideOverlay() {
overlayEntry!.remove();
overlayEntry == null;
}
#override
Widget build(BuildContext context) {
print("=====> build");
return Positioned(
top: widget.focusNode.offset.dy + 50,
left: 0,
child: Material(
elevation: 4.0,
color: Colors.transparent,
child: Container(
width: MediaQuery.of(context).size.width * 0.9,
color: Colors.lightBlueAccent,
child: Row(
children: [
Expanded(
child: Text(
'Show tag here',
style: TextStyle(
fontSize: 20.0,
),
),
),
],
),
)),
);
}
}

Related

how to send Data to other screen?

import 'package:flutter/material.dart';
import 'package:flutter_bmi_app/second_screen.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class BmiCalc extends StatefulWidget {
const BmiCalc({Key? key}) : super(key: key);
#override
State<BmiCalc> createState() => _BmiCalcState();
}
class _BmiCalcState extends State<BmiCalc> {
Color colorOfLittleBox = Color.fromARGB(255, 27, 28, 48);
Color colorOfLittleBox2 = Colors.pink;
bool isMale = true;
double _value = 150;
int weight = 60;
int age = 25;
double answer = 10;
String calc = "CALCULATE";
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromARGB(255, 12, 9, 34),
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
Row(
children: [
FemaleBox("MALE", Icons.male),
FemaleBox("FEMALE", Icons.female),
],
),
Column(children: [
Container(
padding: EdgeInsets.all(32),
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Color.fromARGB(255, 27, 28, 48),
borderRadius: BorderRadius.circular(15),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("HEIGHT",
style: TextStyle(color: Colors.grey, fontSize: 20)),
const SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_value.toStringAsFixed(0),
style: const TextStyle(
fontSize: 45,
color: Colors.white,
fontWeight: FontWeight.w900)),
const Text(
"cm",
style:
TextStyle(fontSize: 20, color: Colors.grey),
),
],
),
Slider(
min: 100,
max: 230,
thumbColor: Colors.pink,
value: _value,
onChanged: (value) {
setState(() {
_value = value;
});
},
),
],
))
]),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Operation("Weight"),
Operation("Age"),
],
),
Container(
decoration: BoxDecoration(
color: Colors.pink,
borderRadius: BorderRadius.circular(15),
),
padding: EdgeInsets.only(bottom: 5),
width: MediaQuery.of(context).size.width,
child: TextButton(
child: Text(
calc,
style: const TextStyle(
fontSize: 22,
color: Colors.white,
fontWeight: FontWeight.w900),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
},
),
)
],
),
),
),
);
}
void calculate() {
answer = (weight / (_value * _value)) * 10000;
Text(answer.toString(),
style: const TextStyle(fontSize: 40, color: Colors.white));
if (calc == "CALCULATE") {
calc = answer.toStringAsFixed(1);
} else {
calc = "CALCULATE";
}
setState(() {});
}
}
I made bmi calculator, I wanna have answer on other screen. I want to send this function calculate() to the second screen, where I will
have the answer of this calculation. I gave Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()), but how to make it work? Thank you in advance.
Make the SecondScreen constructor take a parameter for the type of data that you want to send to it.
const SecondScreen(
{Key? key,required this.answer, })
: super(key: key);
final String? answer; //define value you want to pass
#override
_SecondScreenScreenState createState() => _SecondScreenState();
}
And pass data when navigate
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(answer: 'Hello',),
));
here is the example:
import 'package:flutter/material.dart';
class Todo {
final String title;
final String description;
const Todo(this.title, this.description);
}
void main() {
runApp(
MaterialApp(
title: 'Passing Data',
home: TodosScreen(
todos: List.generate(
20,
(i) => Todo(
'Todo $i',
'A description of what needs to be done for Todo $i',
),
),
),
),
);
}
class TodosScreen extends StatelessWidget {
const TodosScreen({Key? key, required this.todos}) : super(key: key);
final List<Todo> todos;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Todos'),
),
body: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(todos[index].title),
// When a user taps the ListTile, navigate to the DetailScreen.
// Notice that you're not only creating a DetailScreen, you're
// also passing the current todo through to it.
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(todo: todos[index]),
),
);
},
);
},
),
);
}
}
class DetailScreen extends StatelessWidget {
// In the constructor, require a Todo.
const DetailScreen({Key? key, required this.todo}) : super(key: key);
// Declare a field that holds the Todo.
final Todo todo;
#override
Widget build(BuildContext context) {
// Use the Todo to create the UI.
return Scaffold(
appBar: AppBar(
title: Text(todo.title),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(todo.description),
),
);
}
}
I can't get the FemaleBox and Operation in your project so I can't run that try the above example or share you full code include second screen also
add a constructor in your second screen and pass it while calling second screen
const SecondScreen(
{Key? key,required this.answer, })
: super(key: key);
final String? answer; //define value you want to pass
#override
_SecondScreenScreenState createState() => _SecondScreenState();
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(answer: 'Hello',),
));
There is another way to do that.
Create a class as given below and use static keyword to define any variable.
Now you can call this variable at your entire app via- Common.sharedData
So you can modified it according to you
Class Common{
static int sharedData=0;
//Other function
}
There are different ways to solve this
Sending parameters through constructor (Good solution).
Use a State Management package and hold the state in its class and access tit everywhere (Recommended way).
declare variable globally and use it anywhere in the app (not Recommended)

Unable to open keyboard when checking MediaQuery of bottom insets in flutter

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),
],
),
),
),
),
);
}
}

Keyboard automatically disappears from TextField in ListView.Builder

I'm trying to implement a solution where a row (containing both a TextField and a Text) in ListView.Builder is automatically for every record retrieved from a webserver.
However when I want to start typing in such a TextField the keyboard appears and immediatly disappears again.
This is the code of my screen.
class GameScreen extends StatelessWidget {
static const RouteName = "/GameScreen";
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
const horizontalMargin = 20.0;
return Scaffold(
appBar: getAppBar(),
backgroundColor: Colors.transparent,
body: Stack(
children: <Widget>[
Background(),
Column(
children: <Widget>[
Header("Starting letter: B"),
Expanded(
child: ListBlocProvider(
listWidget: GameCategoriesList(),
itemsService: CategoriesService(),
margin: EdgeInsets.only(
left: horizontalMargin,
bottom: 10,
right: horizontalMargin,
),
),
),
SizedBox(
height: 20,
),
SizedBox(
width: size.width - 40,
height: 60,
child: Container(
height: 60,
child: TextButtonWidget(
() {
// Navigator.of(context).pushNamed(GameScreen.RouteName);
},
"Stop game",
),
),
),
SizedBox(
height: 20,
)
],
),
],
),
);
}
}
This is the code of my ListBlocProvider:
class ListBlocProvider extends StatelessWidget {
final ListWidget listWidget;
final ItemsService itemsService;
final bool useColor;
final bool usePaddingTop;
final double height;
final EdgeInsets margin;
const ListBlocProvider({
#required this.listWidget,
#required this.itemsService,
this.useColor = true,
this.usePaddingTop = true,
this.height = 200,
this.margin,
});
#override
Widget build(BuildContext context) {
const horizontalMargin = 20.0;
return BlocProvider(
create: (context) => ItemsBloc(itemsService: itemsService)..add(ItemsFetched()),
child: Container(
padding: usePaddingTop ? EdgeInsets.only(top: 10) : null,
decoration: BoxDecoration(
color: this.useColor ? Color.fromRGBO(10, 50, 75, 0.9) : null,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
margin: this.margin,
height: this.height,
child: this.listWidget,
),
);
}
}
This is the code of my List:
class GameCategoriesList extends ListWidget {
#override
_GameCategoriesListState createState() => _GameCategoriesListState();
}
class _GameCategoriesListState extends State<GameCategoriesList> {
#override
Widget build(BuildContext context) {
return BlocBuilder<ItemsBloc, ItemsState>(
builder: (context, state) {
if (state is ItemsFailure) {
return Center(
child: Text('failed to fetch categories'),
);
}
if (state is ItemsSuccess) {
if (state.items.isEmpty) {
return Center(
child: Text('no categories found.'),
);
}
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
var textEditingController = TextEditingController();
return GameCategoryItemWidget(
key: UniqueKey(),
categoryModel: state.items[index],
textEditingController: textEditingController,
);
},
itemCount: state.items.length,
);
}
return Center(
child: LoadingIndicator(),
);
},
);
}
}
And this is the code where the both the TextField and the Text are build:
class GameCategoryItemWidget extends StatefulWidget {
final CategoryModel categoryModel;
final TextEditingController textEditingController;
const GameCategoryItemWidget({Key key, this.categoryModel, this.textEditingController}) :
super(key: key);
#override
_GameCategoryItemWidgetState createState() => _GameCategoryItemWidgetState();
}
class _GameCategoryItemWidgetState extends State<GameCategoryItemWidget> {
var formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Container(
child: Form(
key: this.formKey,
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 10, top: 20, bottom: 10),
child: Text(
this.widget.categoryModel.name,
style: TextStyle(
color: Colors.white,
fontSize: 18,
),
),
),
Container(
color: Colors.white,
child: InputField(
InputDecoration(labelText: this.widget.categoryModel.name),
this.widget.textEditingController,
false,
),
),
],
),
),
);
}
#override
void dispose() {
this.widget.textEditingController.dispose();
super.dispose();
}
}
The InputField is a custom widget to hide the switch between a Material and a Cupertino version of the TextField.
I've already tried to remove the Key from the custom TextField widget. The funny part is that the input is actually working, however it can't determine for which of the TextFields in the ListView the input is determined so it adds the input to all of them. I've also tried to swap things around with making Stateless widgets Statefull, but that didn't help either.
The entire build is based upon: https://bloclibrary.dev/#/flutterinfinitelisttutorial.
Hoping you guys can help me.

how to change layout in Flutter

I've been trying to design the layout of my ExpansionTile just like the design below but I couldn't figure out how to change the layout. any suggestion on how to change the border radius, change the background color and also make a gap between each other?.
I tried adding boxDecoration in each container but the style only apply to outside but not on each expansionTile.
import 'package:flutter/material.dart';
class MyReoderWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ReorderItems(topTen: ['j']);
}
}
class DataHolder {
List<String> parentKeys;
Map<String, List<String>> childMap;
DataHolder._privateConstructor();
static final DataHolder _dataHolder = DataHolder._privateConstructor();
static DataHolder get instance => _dataHolder;
factory DataHolder.initialize({#required parentKeys}) {
_dataHolder.parentKeys = parentKeys;
_dataHolder.childMap = {};
for (String key in parentKeys) {
_dataHolder.childMap.putIfAbsent(
}
return _dataHolder;
}
}
class ReorderItems extends StatefulWidget {
final List<String> topTen;
ReorderItems({this.topTen});
#override
_ReorderItemsState createState() => _ReorderItemsState();
}
class _ReorderItemsState extends State<ReorderItems> {
#override
void initState() {
super.initState();
// initialize the children for the Expansion tile
// This initialization can be replaced with any logic like network fetch or something else.
DataHolder.initialize(parentKeys: widget.topTen);
}
#override
Widget build(BuildContext context) {
return PrimaryScrollController(
key: ValueKey(widget.topTen.toString()),
controller: ScrollController(),
child: Container(
decoration: BoxDecoration(),
child: ReorderableListView(
onReorder: onReorder,
children: getListItem(),
),
),
);
}
List<ExpansionTile> getListItem() => DataHolder.instance.parentKeys
.asMap()
.map((index, item) => MapEntry(index, buildTenableListTile(item, index)))
.values
.toList();
ExpansionTile buildTenableListTile(String mapKey, int index) => ExpansionTile(
key: ValueKey(mapKey),
title: Text(mapKey),
leading: Icon(Icons.list),
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20))
),
key: ValueKey('$mapKey$index'),
height: 200,
child: Container(
padding: EdgeInsets.only(left: 30.0),
child: ReorderList(
parentMapKey: mapKey,
),
),
),
],
);
void onReorder(int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
setState(() {
String game = widget.topTen[oldIndex];
DataHolder.instance.parentKeys.removeAt(oldIndex);
DataHolder.instance.parentKeys.insert(newIndex, game);
});
}
}
class ReorderList extends StatefulWidget {
final String parentMapKey;
ReorderList({this.parentMapKey});
#override
_ReorderListState createState() => _ReorderListState();
}
class _ReorderListState extends State<ReorderList> {
#override
Widget build(BuildContext context) {
return PrimaryScrollController(
controller: ScrollController(),
child: ReorderableListView(
// scrollController: ScrollController(),
onReorder: onReorder,
children: DataHolder.instance.childMap[widget.parentMapKey]
.map(
(String child) => Container(
child: ListTile(
key: ValueKey(child),
leading: Icon(Icons.list),
title: Text(child),
),
),
)
.toList(),
),
);
}
void onReorder(int oldIndex, int newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
List<String> children = DataHolder.instance.childMap[widget.parentMapKey];
String game = children[oldIndex];
children.removeAt(oldIndex);
children.insert(newIndex, game);
DataHolder.instance.childMap[widget.parentMapKey] = children;
// Need to set state to rebuild the children.
setState(() {});
}
}
You can do it using custom expandable container.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Calendar',
theme: ThemeData(
primarySwatch: Colors.grey,
),
debugShowCheckedModeBanner: false,
home: Material(
child: MyReoderWidget(),
),
);
}
}
class CustomModel {
String title;
bool isExpanded;
List<String> subItems;
CustomModel({this.title, this.subItems, this.isExpanded = false});
}
class MyReoderWidget extends StatefulWidget {
#override
_MyReoderWidgetState createState() => _MyReoderWidgetState();
}
class _MyReoderWidgetState extends State<MyReoderWidget> {
List<CustomModel> listItems;
#override
void initState() {
super.initState();
listItems = List<CustomModel>();
listItems.add(CustomModel(
title: "App Name 1", subItems: ["Card Name 1", "Card Name 2"]));
listItems.add(CustomModel(
title: "App Name 2", subItems: ["Card Name 3", "Card Name 4"]));
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: ListView(
children: listItems
.map((model) => new Padding(
padding: EdgeInsets.only(
bottom: 10,
),
child: ExpandableCardContainer(
isExpanded: model.isExpanded,
collapsedChild: createHeaderCard(model),
expandedChild: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(
bottom: 10,
),
child: createHeaderCard(model),
)
]..addAll(model.subItems
.map((e) => createChildCard(e))
.toList()),
),
),
))
.toList()),
);
}
Widget createHeaderCard(CustomModel model) {
return Container(
child: Row(
children: <Widget>[
Icon(
Icons.more_vert,
color: Colors.white,
),
Expanded(
child: Text(
model.title,
style: TextStyle(color: Colors.white),
),
),
GestureDetector(
onTap: () {
setState(() {
model.isExpanded = !model.isExpanded;
});
},
child: Icon(
model.isExpanded
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down,
color: Colors.white,
),
)
],
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Color(0xFF132435),
),
height: 50,
);
}
Widget createChildCard(String subItems) {
return Container(
margin: EdgeInsets.only(left: 30, bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.more_vert,
color: Colors.white,
),
Expanded(
child: Text(
subItems,
style: TextStyle(color: Colors.white),
),
),
],
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Color(0xFF132435),
),
height: 50,
);
}
}
class ExpandableCardContainer extends StatefulWidget {
final bool isExpanded;
final Widget collapsedChild;
final Widget expandedChild;
const ExpandableCardContainer(
{Key key, this.isExpanded, this.collapsedChild, this.expandedChild})
: super(key: key);
#override
_ExpandableCardContainerState createState() =>
_ExpandableCardContainerState();
}
class _ExpandableCardContainerState extends State<ExpandableCardContainer> {
#override
Widget build(BuildContext context) {
return new AnimatedContainer(
duration: new Duration(milliseconds: 200),
curve: Curves.easeInOut,
child: widget.isExpanded ? widget.expandedChild : widget.collapsedChild,
);
}
}

Method is called twice in StreamBuilder which contains custom dialog in Flutter

I create a loading dialog and put it in StreamBuilder. At the same time, there is a method named _loadingText as the dialog parameter. When I click the 'Go Run' button, the _loadingText method is called twice.
As the same way, I used the flutter build-in dialog showAboutDialog, everything is OK.
If I remove the StreamBuilder, the _loadingText is called once too.
It takes me one day!!!
Any help is appreciated. Thanks in advance...
main.dart:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:view_animation/loading_dialog.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
StreamController<String> _streamController;
TextEditingController _inputController;
#override
void initState() {
super.initState();
_streamController = StreamController<String>.broadcast();
_inputController = TextEditingController();
_inputController.addListener(() {
_streamController.add(_inputController.text);
});
}
#override
void dispose() {
super.dispose();
_streamController.close();
}
String _loadingText() {
print('===== 2. Method run OVER =====');
return 'Loading...';
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_inputContainer(),
SizedBox(
height: 20,
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(26),
),
child: StreamBuilder(
stream: _streamController.stream.map((text) => text.length > 4),
builder: (context, snap) {
return FlatButton(
color: Color(0xFFFFAC0B),
disabledColor: Colors.black12,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
padding: EdgeInsets.symmetric(vertical: 15, horizontal: 12.5),
onPressed: snap.data != null && snap.data
? () {
print('===== 1. show dialog =====');
showDialog(
context: context,
builder: (BuildContext context) {
return LoadingDialog(
loadingText: _loadingText(),
);
});
// showAboutDialog(context: context, applicationName: _loadingText());
}
: null,
child: Text(
'GO RUN',
style: TextStyle(fontSize: 12, color: Colors.white),
),
);
},
),
),
],
)),
);
}
Widget _inputContainer() {
return Container(
width: 200,
padding: EdgeInsets.only(left: 20, right: 20),
decoration: BoxDecoration(
color: Color(0xFFFFAC0B),
borderRadius: BorderRadius.circular(36.0),
),
child: TextField(
controller: _inputController,
keyboardType: TextInputType.number,
maxLines: 1,
cursorColor: Colors.orange,
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Let's GO",
hintStyle: TextStyle(color: Colors.white54, fontSize: 20),
),
),
);
}
}
loading_dialog.dart
import 'package:flutter/material.dart';
class LoadingDialog extends StatefulWidget {
final String loadingText;
final bool outsideDismiss;
final Function dismissCallback;
final Future<dynamic> requestCallback;
LoadingDialog(
{Key key,
this.loadingText = "Loading...",
this.outsideDismiss = true,
this.dismissCallback,
this.requestCallback,
})
: super(key: key);
#override
_LoadingDialogState createState() => _LoadingDialogState();
}
class _LoadingDialogState extends State<LoadingDialog> {
void _dismissDialog(){
if(widget.dismissCallback != null) {
widget.dismissCallback();
}
Navigator.of(context).pop();
}
#override
void initState() {
print('===== 3. loading init =====');
if (widget.requestCallback != null) {
widget.requestCallback.then((_) => Navigator.of(context).pop());
}
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.outsideDismiss ? _dismissDialog : null,
child: Material(
type: MaterialType.transparency,
child: Center(
child: SizedBox(
width: 120.0,
height: 120.0,
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0)
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new CircularProgressIndicator(),
new Padding(
padding: const EdgeInsets.only(
top: 20.0,
),
child: new Text(
widget.loadingText,
style: new TextStyle(fontSize: 12.0),
),
),
],
),
),
),
),
),
);
}
}
log gif here
That's because when you tap on button first time your TextField is still active that means new state comes and flutter rebuilds itself. When you tap on button second your Textfield is inactive.
The points are when you pass the function to the onTap widget it's going to execute when it building state and calling a function without tapping on it:
So instead of a passing method to the OnTap, try something like this:
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () widget.outsideDismiss ? ()
{
this._dismissDialog();
} : null,
...