How to pass parameters using pushNamedAndRemoveUntil? - flutter

I need to pass parameters to the initial page using pushNamedAndRemoveUntil .
I need to re-pass an arg that takes color and textController.text to valueText and color on the initial page. You need to do this with pushNamedAndRemoveUntil.
Please help implement this functionality.
Screen with arg:
class TextValue extends StatefulWidget {
static const routeName = '/text_value';
const TextValue({Key? key}) : super(key: key);
#override
State<TextValue> createState() => _TextValueState();
}
class _TextValueState extends State<TextValue> {
// controller for textField
TextEditingController textController = TextEditingController();
#override
void dispose() {
textController.dispose();
super.dispose();
}
//arg variable
ColorArguments? arg;
#override
Widget build(BuildContext context) {
//get arg from ColorPickerScreen
arg ??= ModalRoute.of(context)!.settings.arguments as ColorArguments;
return Scaffold(
backgroundColor: arg?.color,
appBar: AppBar(
title: const Text('Enter a value'),
centerTitle: true,
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
controller: textController,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'(^\d*\.?\d*)'))
],
style: const TextStyle(color: Colors.black),
decoration: const InputDecoration(
hintText: 'Enter a value',
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.black, width: 2)),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.black, width: 2))),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (textController.text.isEmpty) {
} else {
Navigator.pushNamedAndRemoveUntil(
context, HomeScreen.routeName, (route) => false,
);
}
},
child: const Text('Done'),
)
],
),
),
);
}
}
Initial Screen:
import 'package:flutter/material.dart';
import 'package:flutter_app/screens/color_picker_screen.dart';
class HomeScreen extends StatefulWidget {
final String valueText;
final ColorArguments? color;
static const routeName = '/home';
const HomeScreen({Key? key, required this.valueText, required this.color})
: super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
// navigation to the next screen
void _colorScreen() {
Navigator.push(context,
MaterialPageRoute(builder: (context) => const ColorPicker()));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
centerTitle: true,
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _colorScreen, child: const Text('Choose a color')),
const SizedBox(height: 30.0),
TextFormField(
readOnly: true,
initialValue: widget.valueText,
),
const SizedBox(height: 100),
Container(
width: 50,
height: 50,
color: widget.color?.color,
),
],
),
),
);
}
}

You can use the named arguments parameter of the pushNamedAndRemoveUntil method. All you need to do is create a custom object for passing all the values you need to the push call. The arguments can be accessed in your initial screen by using
final args = ModalRoute.of(context)!.settings.arguments
You can refer to this Flutter cookbook on navigation using named routes with arguments.

Why dont you consider using Class contructors and passing data like this
Navigator.push(context,
MaterialPageRoute(builder: (context) => const ColorPicker(<Your_data>)));

Related

How do I make a text field appear when I click a widget?

Hi, I'm new to the flutter and looking for a way to expand a widget so that when I click on it, I get a text field that allows me to input data from the user.
So far, I've tried dynamic test fields or gesture detectors, but I couldn't find the answer I wanted, so I'm asking questions.
Is there any class that I can refer to?
You need to do few things...
create a variable
bool textFieldDisplayed = false;
Wrap your widget with GestureDetector and use onTap of GestureDetector.
onTap: () {
textFieldDisplayed = true;
setState(() {});
},
check the condition before your textField
if(textFieldDisplayed)
TextFormField()
The whole code is below and you can make some changes as per yours....
class _MyHomePageState extends State<MyHomePage> {
TextEditingController controller = TextEditingController();
bool textFieldDisplayed = false;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
textFieldDisplayed = false;
setState(() {});
},
child: Scaffold(
body: GestureDetector(
onTap: () {
textFieldDisplayed = true;
setState(() {});
},
child: Center(
child: Container(
color: Colors.blue,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if(textFieldDisplayed)
Padding(
padding: const EdgeInsets.all(10.0),
child: SizedBox(
width: 100,
child: TextFormField(
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(width: 1, color: Colors.white),
borderRadius: BorderRadius.circular(5.0),
),
border: OutlineInputBorder(
borderSide: const BorderSide(width: 1, color: Colors.white),
borderRadius: BorderRadius.circular(5.0),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(width: 1,color: Colors.white),
borderRadius: BorderRadius.circular(5.0),
),
),
controller: controller,
),
),
),
Container(height: 20,width: 100,)
],
),
),
),
),
),
);
}
}
You can achieve this using the Visibility widget. Rohan's answer is correct but I wouldn't recommend using if statements in building widgets in a list since it makes the code look messy. I'll put and example bellow:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool showWidget = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Column(
children: [
Visibility(
visible: showWidget,
child: MyWidget()
),
MyButton(
onTap: (){
setState((){
showWidget = !showWidget;
});
}
)
],
),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text(
'Hello, World!',
style: Theme.of(context).textTheme.headline4,
);
}
}
class MyButton extends StatelessWidget {
final Function() onTap;
const MyButton({required this.onTap});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: const Text('Press me!')
);
}
}
When visibility's value is true, it will display the content of its child property. Otherwise it will return a const SizedBox.shrink() by default. Or, you can change whatever widget you want to return adding the 'replacement' property.
Copy the code above and try on a new DartPad. Good Luck!

Getx not updating list of TextFormField correctly

I am using Getx and ListView.builder in a Flutter Web app to render a list of items with TextFormField. Each rendered item has a delete button. When I click to delete an item, the list containing the data seems to update correctly but the corresponding UI incorrectly removes the absolute last item displayed instead of the actual item that you clicked to delete. This problem seems to happen with TextFormField specifically.
I have included below a sample app that illustrates the problem. To test, just install Getx, then run the app (I run it as a Web app). Once the app is running, in the left column (named 'Using TextFormFields'), try to delete items and you'll see the problem -- it is always the last item displayed that deletes, even if you click to delete the first item. For comparison, I have included on right side a set up using ListTiles instead of TextFormFields and that works without a problem.
Does anyone know why this problem occurs with TextFormField specifically? Do you know how to solve this? Thanks in advance for any help!
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class Item {
final int id;
final String name;
Item({
required this.id,
required this.name,
});
}
class OverviewPageController extends GetxController {
final itemsList = [
Item(id: 0, name: 'Item 1'),
Item(id: 1, name: 'Item 2'),
Item(id: 2, name: 'Item 3'),
Item(id: 3, name: 'Item 4'),
].obs;
void deleteItem(Item item) {
int index = itemsList.indexOf(item);
var itemRemoved = itemsList.removeAt(index);
print('item deleted: ${itemRemoved.name}');
itemsList.refresh();
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const GetMaterialApp(
debugShowCheckedModeBanner: false,
home: OverviewPage(),
);
}
}
class OverviewPage extends StatelessWidget {
const OverviewPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(40),
child: Row(
children: const [
Expanded(child: TextFormFieldsSection()),
SizedBox(width: 40),
Expanded(child: ListTilesSection()),
],
),
),
),
);
}
}
class ListTilesSection extends StatelessWidget {
const ListTilesSection({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final controller1 = Get.put(OverviewPageController(), tag: '1');
return Column(
children: [
const Text('Using ListTiles', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
Obx(
() => ListView.builder(
shrinkWrap: true,
itemCount: controller1.itemsList.length,
itemBuilder: (context, index) {
return Column(
children: [
ListTile(
title: Text(controller1.itemsList[index].name),
trailing: OutlinedButton(
onPressed: () => controller1.deleteItem(controller1.itemsList[index]),
child: const Icon(Icons.delete_forever),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: const BorderSide(color: Colors.black12),
),
),
const SizedBox(height: 5),
],
);
},
),
),
],
);
}
}
class TextFormFieldsSection extends StatelessWidget {
const TextFormFieldsSection({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final controller2 = Get.put(OverviewPageController(), tag: '2');
return Column(
children: [
const Text('Using TextFormFields', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
Obx(
() => ListView.builder(
shrinkWrap: true,
itemCount: controller2.itemsList.length,
itemBuilder: (context, index) {
return Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.black12)
),
child: Row(
children: [
Expanded(
child: TextFormField(
readOnly: true,
initialValue: controller2.itemsList[index].name,
decoration: const InputDecoration(border: InputBorder.none),
),
),
OutlinedButton(
onPressed: () => controller2.deleteItem(controller2.itemsList[index]),
child: const Icon(Icons.delete_forever),
),
],
),
),
const SizedBox(height: 5),
],
);
},
),
),
],
);
}
}
you're code is totally right and should work fine, it's Flutter that doesn't know which widget to exactly delete, I will explain:
when Flutter engine notices that an existent element in the tree is deleted from the tree ( which your code does ) it look to replace it with some other widget with it's same runtimeType, so when you wanna delete a TextFormField, Flutter mistaken the exact widget to remove even if your code is totally fine.
This behavior is really helpful for performance in most cases, to avoid extra necessary builds in the tree when it should.
The Answer of your problem is by telling Flutter that each TextFormField is unique from all others, by assigning a unique Key to the TextFormField, Here is your new code:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class Item {
final int id;
final String name;
Item({
required this.id,
required this.name,
});
}
class OverviewPageController extends GetxController {
final itemsList = [
Item(id: 0, name: 'Item 1'),
Item(id: 1, name: 'Item 2'),
Item(id: 2, name: 'Item 3'),
Item(id: 3, name: 'Item 4'),
].obs;
void deleteItem(Item item) {
int index = itemsList.indexOf(item);
var itemRemoved = itemsList.removeAt(index);
print('item deleted: ${itemRemoved.name}');
itemsList.refresh();
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const GetMaterialApp(
debugShowCheckedModeBanner: false,
home: OverviewPage(),
);
}
}
class OverviewPage extends StatelessWidget {
const OverviewPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(40),
child: Row(
children: const [
Expanded(child: TextFormFieldsSection()),
SizedBox(width: 40),
Expanded(child: ListTilesSection()),
],
),
),
),
);
}
}
class ListTilesSection extends StatelessWidget {
const ListTilesSection({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final controller1 = Get.put(OverviewPageController(), tag: '1');
return Column(
children: [
const Text('Using ListTiles',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
Obx(
() => ListView.builder(
shrinkWrap: true,
itemCount: controller1.itemsList.length,
itemBuilder: (context, index) {
return Column(
key: UniqueKey(), // add UniqueKey()
children: [
ListTile(
key: UniqueKey(),
title: Text(controller1.itemsList[index].name),
trailing: OutlinedButton(
onPressed: () =>
controller1.deleteItem(controller1.itemsList[index]),
child: const Icon(Icons.delete_forever),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: const BorderSide(color: Colors.black12),
),
),
const SizedBox(height: 5),
],
);
},
),
),
],
);
}
}
class TextFormFieldsSection extends StatelessWidget {
const TextFormFieldsSection({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final controller2 = Get.put(OverviewPageController(), tag: '2');
return Column(
children: [
const Text('Using TextFormFields',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
Obx(
() => ListView.builder(
shrinkWrap: true,
itemCount: controller2.itemsList.length,
itemBuilder: (context, index) {
return Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.black12)),
child: Row(
children: [
Expanded(
child: TextFormField(
key: UniqueKey(), // add UniqueKey()
readOnly: true,
initialValue: controller2.itemsList[index].name,
decoration:
const InputDecoration(border: InputBorder.none),
),
),
OutlinedButton(
onPressed: () => controller2
.deleteItem(controller2.itemsList[index]),
child: const Icon(Icons.delete_forever),
),
],
),
),
const SizedBox(height: 5),
],
);
},
),
),
],
);
}
}
Note the UniqueKey() I assigned to widgets, now run your app again and it should work as you expect.
Please, refer also to those topics:
When to Use Keys - Flutter Widget
Key

Error: Could not find the correct Provider<PassBloc> above this AddPassWidget Widget

I'm using bloc to handle the state of a password manager app. But I get the error: Error: Could not find the correct Provider above this AddPassWidget Widget
I have placed BlocProvider in home_screen where I show the passwords in a listview. There is a floating action button which is used to add new password. The error occur while clicking on save button. I guess I'm providing wrong context in it but can't figure out where I'm missing.
My code looks like:
Home Screen:
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) =>
PassBloc(context.read<PassRepository>())..add(PassLoadEvent()),
child: Scaffold(
appBar: _appBar(),
body: _bodyView(),
drawer: const SideMenu(),
floatingActionButton: _floatingActionButton(),
),
);
}
Widget _bodyView() {
return BlocBuilder<PassBloc, PassState>(
builder: (context, state) {
if (state is PassInitial) {
return _loadingView();
} else if (state is PassLoadedState) {
return state.passList.isEmpty
? _emptyPassView()
: _passListView(state.passList);
}
return _loadingView();
},
);
}
Widget _floatingActionButton() {
return FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () => showModalBottomSheet(
context: context,
builder: (context) => const AddPassWidget(),
),
);
}
Add pass dialog
class AddPassWidget extends StatefulWidget {
const AddPassWidget({Key? key}) : super(key: key);
#override
State<AddPassWidget> createState() => _AddPassWidgetState();
}
class _AddPassWidgetState extends State<AddPassWidget> {
final TextEditingController _websiteTEC = TextEditingController();
final TextEditingController _userNameTEC = TextEditingController();
final TextEditingController _passwordTEC = TextEditingController();
Uuid uuid = const Uuid();
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
const Text(
"Add Password",
style: TextStyle(fontSize: 20),
),
const SizedBox(height: 15),
TextFormField(
autocorrect: false,
autofocus: true,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: "Website Name",
),
controller: _websiteTEC,
),
const SizedBox(height: 10),
TextFormField(
autocorrect: false,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: "Username",
),
controller: _userNameTEC,
),
const SizedBox(height: 10),
TextFormField(
obscureText: true,
enableSuggestions: false,
autocorrect: false,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: "Password",
),
controller: _passwordTEC,
),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Cancel")),
ElevatedButton(
onPressed: () {
context.read<PassBloc>().add(PassAddEvent(
pass: PassModel(
id: uuid.v4(),
websiteName: _websiteTEC.text,
username: _userNameTEC.text,
password: _passwordTEC.text,
),
));
Navigator.pop(context);
},
child: const Text('Save'),
),
],
),
],
),
),
),
);
}
}

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)

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,
...