Detect when widget changes position in Flutter - dart

Is there any way to detect when a widget changes position? Such as when the keyboard pops up and the content is shifted up? I would like to detect this without relying on focus events or trying to detect the keyboard state.
Here's an example app:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MyTextField()
)
)
);
}
}
class MyTextField extends StatelessWidget {
#override
Widget build(BuildContext context) {
return TextField();
}
}
How can you detect when MyTextField is moved up when it is focused?

You can use WidgetsBindingObserver to detect when the metrics change, here you have a sample but you will have to use GlobalKey to check the new position of your Widget :
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
GlobalKey _key = GlobalKey();
#override
void initState() {
WidgetsBinding.instance.addObserver(this);
super.initState();
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeMetrics() {
final RenderBox renderBox = _key.currentContext.findRenderObject();
final position = renderBox.localToGlobal(Offset.zero);
print("position : ${position.dx},${position.dy}");
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: MyTextField(
key: _key,
),
),
);
}
}
class MyTextField extends StatelessWidget {
const MyTextField({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextField();
}
}

If you just want to find out if the keyboard is visible, you can use this:
import 'dart:ui';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeMetrics() {
print('Is the keyboard visible? ${window.viewInsets.bottom != 0 ? 'yes' : 'no'}');
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: TextField(),
),
),
);
}
}
Explaination: By getting the bottom position of the rectangle in which your app is drawn, you can deduce whether a system component has reduced the space of your app, hence if the keyboard is visible.
This answers only this part of your question:
Such as when the keyboard pops up and the content is shifted up?
But at least the size of the rectangle in which your app is drawn is always the current one and not an old size. This is the problem with #diegoveloper's answer, you always get the old position of the TextField.

Related

How to maintain Flutter Global BloC state using Provider on Hot Reload?

I seem to lose application state whenever I perform a hot reload.
I am using a BloC provider to store application state. This is passed at the App level in the main.dart and consumed on a child page. On the initial load of the view, the value is shown. I can navigate around the application and the state persists. However, when I perform a hot reload, I lose the values and seemingly the state.
How can I fix this issue so that state is preserved on Hot Reload?
Bloc Provider
abstract class BlocBase {
void dispose();
}
class BlocProvider<T extends BlocBase> extends StatefulWidget {
BlocProvider({
Key key,
#required this.child,
#required this.bloc,
}): super(key: key);
final T bloc;
final Widget child;
#override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context){
final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
return provider.bloc;
}
static Type _typeOf<T>() => T;
}
class _BlocProviderState<T> extends State<BlocProvider<BlocBase>>{
#override
void dispose(){
widget.bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context){
return widget.child;
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return BlocProvider<ApplicationStateBloc>(
bloc: ApplicationStateBloc(),
child: MaterialApp(
title: 'Handshake',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: LoadingPage(),
)
);
}
}
class ProfileSettings extends StatefulWidget {
#override
_ProfileSettingsState createState() => _ProfileSettingsState();
}
class _ProfileSettingsState extends State<ProfileSettings>{
ApplicationStateBloc _applicationStateBloc;
#override
void initState() {
super.initState();
_applicationStateBloc = BlocProvider.of<ApplicationStateBloc>(context);
}
#override
void dispose() {
_applicationStateBloc?.dispose();
super.dispose();
}
Widget emailField() {
return StreamBuilder<UserAccount>(
stream: _applicationStateBloc.getUserAccount,
builder: (context, snapshot){
if (snapshot.hasData) {
return Text(snapshot.data.displayName, style: TextStyle(color: Color(0xFF151515), fontSize: 16.0),);
}
return Text('');
},
);
}
#override
Widget build(BuildContext context) {
return BlocProvider<ApplicationStateBloc>(
bloc: _applicationStateBloc,
child: Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: <Widget>[
emailField(),
.... // rest of code
class ApplicationStateBloc extends BlocBase {
var userAccountController = BehaviorSubject<UserAccount>();
Function(UserAccount) get updateUserAccount => userAccountController.sink.add;
Stream<UserAccount> get getUserAccount => userAccountController.stream;
#override
dispose() {
userAccountController.close();
}
}
I was facing the same problem. Inherited widgets make it hard disposing bloc's resources.
Stateful widget, on the other hand, allows disposing, but in the implementation you're using it doesn't persist the bloc in the state causing state loss on widgets rebuild.
After some experimenting I came up with an approach that combines the two:
class BlocHolder<T extends BlocBase> extends StatefulWidget {
final Widget child;
final T Function() createBloc;
BlocHolder({
#required this.child,
#required this.createBloc
});
#override
_BlocHolderState createState() => _BlocHolderState();
}
class _BlocHolderState<T extends BlocBase> extends State<BlocHolder> {
T _bloc;
Function hello;
#override
void initState() {
super.initState();
_bloc = widget.createBloc();
}
#override
Widget build(BuildContext context) {
return BlocProvider(
child: widget.child,
bloc: _bloc,
);
}
#override
void dispose() {
_bloc.dispose();
super.dispose();
}
}
Bloc holder creates bloc in createState() and persists it. It also disposes bloc's resources in dispose().
class BlocProvider<T extends BlocBase> extends InheritedWidget {
final T bloc;
const BlocProvider({
Key key,
#required Widget child,
#required T bloc,
})
: assert(child != null),
bloc = bloc,
super(key: key, child: child);
static T of<T extends BlocBase>(BuildContext context) {
final provider = context.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider;
return provider.bloc;
}
#override
bool updateShouldNotify(BlocProvider old) => false;
}
BlocProvider, as the name suggests, is only responsible for providing the bloc to nested widgets.
All the blocs extend BlocBase class
abstract class BlocBase {
void dispose();
}
Here's a usage example:
class RouteHome extends MaterialPageRoute<ScreenHome> {
RouteHome({List<ModelCategory> categories, int position}): super(builder:
(BuildContext ctx) => BlocHolder(
createBloc: () => BlocMain(ApiMain()),
child: ScreenHome(),
));
}
You are losing the state because your bloc is being retrieved in the _ProfileSettingsState's initState() thus, it won't change even when you hot-reload because that method is only called only once when the widget is built.
Either move it to the build() method, just before returning the BlocProvider
#override
Widget build(BuildContext context) {
_applicationStateBloc = BlocProvider.of<ApplicationStateBloc>(context);
return BlocProvider<ApplicationStateBloc>(
bloc: _applicationStateBloc,
child: Scaffold(
backgroundColor: Colors.white,
....
or to the didUpdateWidget method which is called anytime the widget state is rebuild.
Have in mind that if you are using a non-broadcast stream in your bloc you may get an exception if you try to listen to a stream that is already being listened to.

how to send data through different classes in different screens in flutter

i was struck here while making an application my code went like this
void main() {
runApp(Myapp());
}
class Myapp extends StatelessWidget {
bool s=false;
#override
Widget build(BuildContext context) {
return (MaterialApp(
debugShowCheckedModeBanner: false,
title: "haha app",
theme: ThemeData(primarySwatch: Colors.lime),
home: s ? HomeScreen(null) : LoginPage()));
}
}
the above code is of main.dart file
and this is my another file called Login.dart and the code goes like this
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
Widget build(BuildContext context) {
return(some button ontap:(\\ on tap on this i have to change the bool s value in main.dart to true how to do that){
}
)
}
on tap the button the value s in main dart file should change to true but without navigator because we are not navigating here just a click.
please help me,
thanks in advance
You can use callbacks to communicate your widgets, like this
Create a method to get the callback , in this case : onChangeBool , pass the callback to your LoginPage Widget.
class Myapp extends StatelessWidget {
bool s=false;
onChangeBool(){
//change your var here
s = true;
//refresh the state
setState(() {
});
}
#override
Widget build(BuildContext context) {
return (MaterialApp(
debugShowCheckedModeBanner: false,
title: "haha app",
theme: ThemeData(primarySwatch: Colors.lime),
home: s ? HomeScreen(null) : LoginPage(onPressed: () => onChangeBool() ));
}
}
Receive the callBack , and call it when you press the button
class LoginPage extends StatefulWidget {
final VoidCallback onPressed;
LoginPage({this.onPressed});
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
Widget build(BuildContext context) {
return RaisedButton(
child: Text("button"),
onPressed: (){
widget.onPressed();
},
)
}
)
}
In case you want to pass Data, you can use ValueChanged callback , or if you want to pass complex data, create your own callback using typedef/
A sample using ValueChanged.
class Myapp extends StatelessWidget {
bool s=false;
receiveData(String data){
print("your text here : $data");
}
#override
Widget build(BuildContext context) {
return (MaterialApp(
debugShowCheckedModeBanner: false,
title: "haha app",
theme: ThemeData(primarySwatch: Colors.lime),
home: s ? HomeScreen(null) : LoginPage(onPressed: receiveData ));
}
}
class LoginPage extends StatefulWidget {
final ValueChanged<String> onPressed;
LoginPage({this.onPressed});
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
Widget build(BuildContext context) {
return RaisedButton(
child: Text("button"),
onPressed: (){
widget.onPressed("passing this data");
},
)
}
)
}

Scrollable Listview in Flutter with Dart

Can someone explain me where I should define a scroll controller? I have chat list view which is the body of a scrollable view. I want to be able to control the scrolling behaviour from MainView but don't know how to pass the controller down to _ChatListView. Any ideas?
mainview.dart
class MainView extends StatelessWidget {
...
// is this the correct place?
final ScrollController scrollController = ScrollController();
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new ChatListView()
);
}
}
chatlistview.dart
class ChatListView extends StatefulWidget {
#override
_ChatListView createState() => _ChatListView();
}
class _ChatListView extends State< ChatListView > {
Widget build(BuildContext context) {
return ListView.builder(
controller: scrollController,
);
}
}
Add a constructor and pass the controller as parameter
class MainView extends StatelessWidget {
...
// is this the correct place?
final ScrollController scrollController = ScrollController();
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new ChatListView(scrollController: scrollController)
);
}
}
class ChatListView extends StatefulWidget {
ChatListView({#required this.scrollController});
final ScrollController scrollController;
#override
_ChatListView createState() => _ChatListView();
}
class _ChatListView extends State< ChatListView > {
Widget build(BuildContext context) {
return ListView.builder(
controller: widget.scrollController,
);
}
}

Flutter Error: MediaQuery.of() called with a context that does not contain a MediaQuery

I have been trying to get the size of the whole context view in Flutter. But every time I try I'm getting the above mentioned error.
Here's my code:
import 'package:flutter/material.dart';
void main => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return new MaterialApp(
home: new Scaffold(),
);
}
}
Note: I also tried with a StatefulWidget.
Please, help me find what I'm doing wrong here.
You need a MaterialApp or a WidgetsApp around your widget. They provide the MediaQuery. When you call .of(context) flutter will always look up the widget tree to find the widget.
You usually have this in your main.dart:
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Title',
theme: kThemeData,
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Container(
child: ...,
);
}
}
What works for us is using WidgetsBinding.instance.window instead of MediaQuery - also when setting the theme of the MaterialApp:
_pixelRatio = WidgetsBinding.instance.window.devicePixelRatio;
_screenWidth = WidgetsBinding.instance.window.physicalSize.width;
_screenHeight = WidgetsBinding.instance.window.physicalSize.height;
_statusBarHeight = WidgetsBinding.instance.window.padding.top;
_bottomBarHeight = WidgetsBinding.instance.window.padding.bottom;
_textScaleFactor = WidgetsBinding.instance.window.textScaleFactor;
You can access MediaQuery when you are inside MaterialApp. The place where you are accessing the media query is not correct.
Please refer below code:
import 'package:flutter/material.dart';
class CommonThings {
static Size size;
}
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'MediaQuery Demo',
theme: new ThemeData(
primarySwatch: Colors.red,
),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
CommonThings.size = MediaQuery.of(context).size;
print('Width of the screen: ${CommonThings.size.width}');
return new Container();
}
}
I've purposely created a class CommonThings which has static Size so that you can use it throughout the app.
I fixed it by using the following method. First I created a new class named MyWidget and returned it in MyApp within a MaterialApp's home:. Refer code below:
import 'package:flutter/material.dart';
void main => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyWidget(),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return new MaterialApp(
home: new Scaffold(),
);
}
}
Also, declaring size as final doesn't matter. Orientation/Rotation is handled.
Solved by re-run the app(click on stop button in android studio then run again)
There is better way. Above solutions would require you to have only one screen widget or inherit all screens from parent class. But there is solution, place the media query initialization into onGenerateRoute callback function
main.dart
import 'package:flutter/material.dart';
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My Awesome App',
routes: NavigationUtils.routeList(),
onGenerateRoute: (routeSettings) =>
NavigationUtils.onGenerateRoute(routeSettings),
);
}
}
NavigationUtils.dart
import 'package:flutter/material.dart';
class NavigationUtils {
static onGenerateRoute(RouteSettings routeSettings) {
return new MaterialPageRoute(
builder: (context) {
WidgetUtils.me.init(context);
return StorageUtils.me.isLogged() ? HomeScreen() : ForkScreen();
},
settings: routeSettings,
);
}
}
WidgetUtils.dart
import 'package:flutter/material.dart';
class WidgetUtils {
MediaQueryData _mediaQueryData;
double _screenWidth;
double _screenHeight;
double _blockSizeHorizontal;
double _blockSizeVertical;
init(BuildContext context) {
_mediaQueryData = MediaQuery.of(context);
screenWidth = _mediaQueryData.size.width;
screenHeight = _mediaQueryData.size.height;
blockSizeHorizontal = screenWidth / 100;
blockSizeVertical = screenHeight / 100;
}
}
Warning: It is not copy & paste code, there are some singletons etc. but you should get the point ;)
Had the same error in
import 'screens/tasks_screen.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return TasksScreen();
}
}
I solved it by:-
import 'package:flutter/material.dart';
import 'screens/tasks_screen.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: TasksScreen(),
);
}
}
Wrap your code in a Material App widget. I also had the same issue as I forgot to use it and directly returned the scaffold.
In other words, your MediaQuery.of(context) should be inside the Material Widget.
Material app -> scaffold -> MediaQuery.of(context)
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyAppOne(),
);
}
}
class MyAppOne extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyAppOne>{
#override
Widget build(BuildContext context){
return Scaffold(
);
}
}
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body:HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size.height;
return Container(
height:size/2,
color:Colors.lightBlueAccent,
);
}
}
YOU SHOULD TRY THIS I HAVE DONE IT.
I was trying to change the package then this error arise,
so make sure you complete each of the following steps
https://stackoverflow.com/a/51550358/4993045
Add MaterialApp ...
void main() {
runApp(MaterialApp(
home: HomePage(),
));
}

ExpansionTile doesn't keep state

following problem:
I have a list of ExpansionTiles which works very well. The only problem I'm facing is that a expanded ExpansionTile which is scrolled out of view will, after scrolling it into view again, no longer be expanded. This leads to undesired user experience and also a kind of "jumpy" scrolling.
The documentation states the following:
When used with scrolling widgets like ListView, a unique key must be specified to enable the ExpansionTile to save and restore its expanded state when it is scrolled in and out of view.
This doesn't work though. So far I have found no way to make this work.
Here is the code so far:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'ExpansionTile Test',
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> _getChildren() {
List<Widget> elements = [];
for (int i = 0; i < 20; i++) {
elements.add(new ListChild());
}
return elements;
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('ExpansionTile Test'),
),
body: new ListView(
children: _getChildren(),
),
);
}
}
class ListChild extends StatefulWidget {
#override
State createState() => new ListChildState();
}
class ListChildState extends State<ListChild> {
GlobalKey<ListChildState> _key = new GlobalKey();
#override
Widget build(BuildContext context) {
return new ExpansionTile(
key: _key,
title: const Text('Test Tile'),
children: <Widget>[
const Text('body'),
],
);
}
}
Use a PageStorageKey instead of a GlobalKey.

Resources