Dismiss keyboard when swiping between pages in PageView - flutter

I'm trying to dismiss the keyboard between swiping between screen2(has a textfield) and screen1 using the PageView widget. I've tried calling Focus.of(context).unfocus(); in the dispose method of screen 2.But the keyboard remains.. Here's a minimum example...
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: const Text('Welcome to Flutter'),
),
body: PageView(
children: [
Screen1(),
Screen2(),
],
)),
);
}
}
class Screen1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text("Screen1");
}
}
class Screen2 extends StatefulWidget {
#override
_Screen2State createState() => _Screen2State();
}
class _Screen2State extends State<Screen2> {
#override
void dispose() {
Focus.of(context).unfocus();
super.dispose();
}
#override
Widget build(BuildContext context) {
return
Column(
children: [
Text("Screen2"),
TextField()
],
);
}
}

If you want to dismiss the keyboard when you're swiping on pages, you can use onPageChanged property, using WidgetsBinding.instance?.focusManager.primaryFocus?.unfocus() will be executed when the user scroll every page and there exist a focus.
I took your code and modified.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: const Text('Welcome to Flutter'),
),
body: PageView(
onPageChanged: (index) {
WidgetsBinding.instance?.focusManager.primaryFocus?.unfocus();
},
children: [
Screen1(),
Screen2(),
],
)
),
);
}
}
class Screen1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text("Screen1");
}
}
class Screen2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
Text("Screen2"),
TextField()
],
);
}
}

Related

How to Maintain State of Drawer's Child Widget

I have a stateful widget, Counter, with a button and a counter that keeps track of how many times the button was pressed. This widget is in the drawer. When I close and open the drawer again, the counter is reset. How do I make it so that the counter is not reset upon closing and opening the drawer?
Here is the code:
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(
home: Scaffold(
drawer: Drawer(
child: Counter(),
),
appBar: AppBar(),
body: Container(),
),
);
}
}
class Counter extends StatefulWidget {
const Counter({super.key});
#override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(_count.toString()),
ElevatedButton(
onPressed: () {
setState(() {
_count = _count + 1;
});
},
child: Text('Increment Counter'),
)
],
);
}
}
To keep state of a variable within the Drawer(), the real solution would be to use a State Management library.
However, what you can do is create a global variable and pass it down the tree to Drawer():
import 'package:flutter/material.dart';
void main() {
runApp( MyApp());
}
class MyApp extends StatelessWidget {
var counter = 0;
MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
drawer: Drawer(
child: Counter(counter: counter,),
),
appBar: AppBar(),
body: Container(),
),
);
}
}
class Counter extends StatefulWidget {
int counter;
Counter({required this.counter,super.key});
#override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(widget.counter.toString()),
ElevatedButton(
onPressed: () {
setState(() {
widget.counter = widget.counter + 1;
});
},
child: Text('Increment Counter'),
)
],
);
}
}

Why can't I get the provider package to listen to a change?

So I'm writing a very simple app, just to learn a little bit more about the provider package in flutter. The app is this:
App functionallity
When I write in mi TextField, the text bellow it should change and the title should remain unchanged, but I can't get it to listen the changes from changeText method. The code is
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Data>(
create: (context) => Data(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
title: MyText(),
),
body: Container(
child: Level1(),
),
),
),
);
}
}
class Level1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Level2(),
);
}
}
class Level2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
MyTextField(),
Level3(),
],
),
);
}
}
class Level3 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text(Provider.of<Data>(context, listen: false).data);
}
}
class MyText extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text(context.watch<Data>().data);
}
}
class MyTextField extends StatelessWidget {
#override
Widget build(BuildContext context) {
return TextField(
onChanged: (newText) {
Provider.of<Data>(context).changeText(newText);
print(newText);
},
);
}
}
class Data extends ChangeNotifier {
String data = 'Hello';
void changeText(String newString) {
data = newString;
print('Hello $data');
notifyListeners();
}
}
Try putting the TextField widget inside a Stateful widget.
return TextField(
onChanged: (newText) {
Provider.of<Data>(context).changeText(newText);
print(newText);
},
);
Enclose this in a stateful widget
Also you have to add a Text controller to the textfield widget, go to docs for textfield widget in flutter.

How to reuse the same layout screen without creating a new widget tree branch

I am developing Flutter Web Application.
The object is to reuse the same layout screen widget(Drawer, AppBar) for most of route screen.
I have tried create a new Scaffold class and add body widget to each screen.
The problem is every time I navigate to a new screen. There is a new (MyScaffold) created on the widget tree. So it is not good for performance.
I also tried to use nested router, the problem is nested router is not supported by url that I can not navigate to the screen by typing the URL.
Is there any other proper way to deal with this problem.
Thanks
Add the code example :
import 'package:flutter/material.dart';
void main() => runApp(AppWidget());
class AppWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => FirstScreen(),
'/second': (context) => SecondScreen(),
},
);
}
}
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Center(
child: RaisedButton(
child: Text('Launch screen'),
onPressed: () {
Navigator.pushReplacementNamed(context, '/second');
},
),
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pushReplacementNamed(context, '/');
},
child: Text('Go back!'),
),
),
);
}
}
And I will try to explain the question better.
As you can see First Screen and Second Screen has Exactly same structure of widget tree. But every time flutter is remove the Screen Widget and create a new one.
I also tried to change the code to create a new MyScaffold and reuse the same Widget class :
class AppWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => MyScallfold(
bodyWidget: FirstScreen(),
),
'/second': (context) => MyScallfold(
bodyWidget: SecondScreen(),
),
},
);
}
}
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
Navigator.pushReplacementNamed(context, '/second');
},
child: Text('To Screen 2!'),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
Navigator.pushReplacementNamed(context, '/');
},
child: Text('To Screen 1!'),
);
}
}
class MyScallfold extends StatelessWidget {
Widget bodyWidget;
MyScallfold({this.bodyWidget});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('WebAppTest'),
),
body: bodyWidget,
);
}
}
Bus I noticed every time I use the navigation, all the widget of the tree is rebuilt (The renderObject #id is changed)
So is it possible to reuse the same RenderObject (AppBar, RichText) in flutter to optimise the performance ?
The quick answer is no, not yet anyway. Currently when you use Navigator it refreshes the page and rebuilds the full view.
The most efficient way on Flutter web currently would be to use a TabController with a TabBarView in a Stateful widget with SingleTickerProviderStateMixin.
It only loads what Widget is on screen, but doesn't require the page to reload to view other pages. Your example would look like this (I have added animation to transition to the next page, but you can remove it):
import 'package:flutter/material.dart';
TabController tabController;
class MainScreen extends StatefulWidget {
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> with SingleTickerProviderStateMixin {
int activeTab = 0;
#override
void initState() {
tabController = TabController(length: 3, vsync: this, initialIndex: 0)
..addListener(() {
setState(() {
activeTab = tabController.index;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('WebAppTest'),
),
body: Expanded(
child: TabBarView(
controller: tabController,
children: <Widget>[
FirstScreen(), //Index 0
SecondScreen(), //Index 1
ThirdScreen(), //Index 2
],
),
),
);
}
}
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
tabController.animateTo(2);
},
child: Text('To Screen 3!'),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
tabController.animateTo(0);
},
child: Text('To Screen 1!'),
);
}
}
class ThirdScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
tabController.animateTo(1);
},
child: Text('To Screen 2!'),
);
}
}

Why is my "Data"."data" not updated in ChangeNotifier class using ChangeNotifierProvider?

I am new to flutter.
I want to ask why when my text field's onChange did not trigger: "Provider.ofContext).updateData(newString);".
The value of my Provider.of(context).data is not updated and with the 2 print statements, only 'called1' is always printed out.
Here is the code:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Data>(
create: (_) => Data(),
lazy: false,
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(Provider.of<Data>(context).data),
),
body: Level2(),
),
),
);
}
}
class Level2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
MyTextField(),
],
);
}
}
class MyTextField extends StatelessWidget {
#override
Widget build(BuildContext context) {
return TextField(onChanged: (newString) {
print('called1');
Provider.of<Data>(context).updateData(newString);
print('called2');
});
}
}
class Data extends ChangeNotifier {
String data = '1234567890';
void updateData(newString) {
data = newString;
notifyListeners();
}
}
You are trying to access provider in same widget where you are declaring, which is not right way to do, provider must declare in above widget where you are accessing.
Moreover always use provider data by variable(as used in MyTextField widget) other wise it will not work.
Following code may help you to understand more.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Data>(
create: (_) => Data(),
child: MaterialApp(home: Level1()),
);
}
}
class Level1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Provider.of<Data>(context).data),
),
body: Level2(),
);
}
}
class Level2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
MyTextField(),
],
);
}
}
class MyTextField extends StatelessWidget {
var dataprovider;
#override
Widget build(BuildContext context) {
dataprovider = Provider.of<Data>(context);
return TextField(
onChanged: (newString) {
print(dataprovider.data);
dataprovider.updateData(newString);
print('called2');
},
);
}
}
class Data extends ChangeNotifier {
String data = '1234567890';
void updateData(newString) {
print("cds");
data = newString;
notifyListeners();
}
}

how to edit value at the child widget

i try to edit the value of the child widget, i can do it with StatefulWidget parent but i want to do it with StatelessWidget parent and without using global value
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
FlatButton(child: Text('addFile'), onPressed: () {}),
FlatButton(child: Text('deleteFile'), onPressed: () {})
],
),
body: Child(),
);
}
}
class Child extends StatefulWidget {
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
var hasFile = true;
#override
Widget build(BuildContext context) {
return hasFile ? Text('has a file') : Text("no File");
}
}
You are thinking the wrong way. Child aka Text() should get its value from a model which is managed by the application or at least managed by the widget above. I would go with the provider package https://pub.dev/packages/provider and do this:
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
class MyState with ChangeNotifier {
String _myText;
MyState(this._myText);
getMyText() => _myText;
void changeText(String newText) {
_myText = newText;
notifyListeners();
}
}
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => MyState("initial Text")),
],
child: Scaffold(
appBar: AppBar(
actions: <Widget>[
FlatButton(
child: Text('addFile'),
onPressed: () {
Provider.of<MyState>(context).changeText("addFile");
}),
FlatButton(
child: Text('deleteFile'),
onPressed: () {
Provider.of<MyState>(context).changeText("deleteFile");
})
],
),
body: Child(),
));
}
}
class Child extends StatelessWidget {
#override
Widget build(BuildContext context) {
MyState myState = Provider.of<MyState>(context);
return Text(myState.getMyText());
}
}
This is coded without IDE support or even compiling and running. But it should get you to the right direction.
You can use BLoC pattern to implement this kind of functionality,
Here is the BLoC class which will handle state of bool
import 'dart:async';
class Bloc {
final _fileController = StreamController<bool>();
changeState(bool val) {
_fileController.sink.add(val);
}
get hasFile => _fileController.stream;
dispose() {
_fileController.close();
}
}
final bloc = Bloc();
Then you can add stream builder in your Stateful Widget, in which you will provide stream of BLoC class.
StreamBuilder updates it's UI according to Stream.
class Child extends StatefulWidget {
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
var hasFile = true;
#override
void dispose() {
bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.hasFile,
initialData: false,
builder: (context, snapshot) {
return snapshot.data ? Text('has a file') : Text("no File");
},
);
}
}
At last you can access BLoC class with your stateless widget as follows
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
FlatButton(
child: Text('addFile'),
onPressed: () {
bloc.changeState(true);
}),
FlatButton(
child: Text('deleteFile'),
onPressed: () {
bloc.changeState(false);
})
],
),
body: Child(),
);
}
}
Full example is as below
import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Homepage(),
);
}
}
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
FlatButton(
child: Text('addFile'),
onPressed: () {
bloc.changeState(true);
}),
FlatButton(
child: Text('deleteFile'),
onPressed: () {
bloc.changeState(false);
})
],
),
body: Child(),
);
}
}
class Child extends StatefulWidget {
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
var hasFile = true;
#override
void dispose() {
bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.hasFile,
initialData: false,
builder: (context, snapshot) {
return snapshot.data ? Text('has a file') : Text("no File");
},
);
}
}
class Bloc {
final _fileController = StreamController<bool>();
changeState(bool val) {
_fileController.sink.add(val);
}
get hasFile => _fileController.stream;
dispose() {
_fileController.close();
}
}
final bloc = Bloc();