Flutter: How can I prevent default behaviour on key press? - events

I'm trying to intercept when a user presses the volume buttons to perform a specific action and prevent the default behaviour (volume changes).
This is the code I have so far:
RawKeyboard.instance.addListener(_keyboardListener);
void _keyboardListener(RawKeyEvent e) {
if(e.runtimeType == RawKeyUpEvent) {
RawKeyEventDataAndroid eA = e.data;
if(eA.keyCode == 24) { //volume up key
_goNextPage();
}
if(eA.keyCode == 25) { //volume down key
_goPrevPage();
}
}
}
How would I go about preventing the volume from changing (and stopping the volume slider from appearing at the top)?
A Javascript analogous would be calling event.preventDefault() on the key event.
This seems to be a rather trivial matter, but I haven't been able to find any answers in the docs.
Thanks.

I've faced a similar problem and what to share how I solved it.
To stop the propagation we have to return true from onKey method of a FocusNode in the focus nodes tree. To achieve this I've wrapped my app body with FocusScope and Focus widgets like this:
MaterialApp(
home: Scaffold(
body: FocusScope(
autofocus: true,
child: Focus(
autofocus: true,
canRequestFocus: true,
onKey: (data, event) {
if (event.isKeyPressed(LogicalKeyboardKey.audioVolumeUp)) {
print("Volume up");
return true;
}
if (event
.isKeyPressed(LogicalKeyboardKey.audioVolumeDown)) {
print("Volume down");
return true;
}
return false;
},
child: Text(text: "Hallochen")))))

Thanks to Sergey's answer I was able to solve the issue as well. In my case, I wanted to create a ListView, with pull to refresh (RefreshIndicator) that will work for both mobile devices and web.
I tried to implement a refresh indicator which will appear when the user clicks F5 to refresh the web page, but I had to prevent the browser from actually refreshing the page.
Here's an example of my implementation, which prevents refresh from occuring when the user clicks F5.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class ExamplePage extends StatefulWidget {
#override
_ExamplePageState createState() => _ExamplePageState();
}
class _ExamplePageState extends State<ExamplePage> {
final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey = new GlobalKey<RefreshIndicatorState>();
List items = [];
Future<void> _pullRefresh() async {
await Future.delayed(Duration(milliseconds: 1000));
}
#override
Widget build(BuildContext context) {
return FocusScope(
autofocus: true,
child: Focus(
autofocus: true,
canRequestFocus: true,
onKey: (data, event) {
if (event
.isKeyPressed(LogicalKeyboardKey.f5)) {
_refreshIndicatorKey.currentState!.show();
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
child: Container(
padding: EdgeInsets.all(15.0),
child: RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: _pullRefresh,
child: AnimatedList(
key: listKey,
initialItemCount: items.length,
itemBuilder: (context, index, animation) {
return _buildItem(context, index, animation);
},
),
),
),
),
);
}
Widget _buildItem(
BuildContext context, int index, Animation<double> animation) {
return Text("Example");
}
}

all the solutions here are for Focus widget, they inspired me but were not quite the solution for me as I was using RawKeyboardListener and did not wanna change to something else.
here is what worked for me:
final node = FocusNode();
final fnode = FocusScopeNode();
#override
Widget build(BuildContext context) {
return FocusScope(
node: fnode,
child: RawKeyboardListener(
focusNode: node,
...
),
);
}

Related

Flutter app crashing when trying to display more than 10 images

I am writing my first Flutter app. The app allows the user to take multiple images (from 1 to 50+), and displays each image on the screen all at once using the ListView.
The issue I am having is, the app crashes after roughly 10/12 pictures on the Samsung SM A520F, am guessing this is due to the fact that this is not a very powerful device.
Is there a way I can display the thumbnail of the image instead of loading the full size image?
Error message:
I don't actually get any error messages, the app just seems to restart!
Here is my code
import 'package:flutter/material.dart';
import 'package:myapp/utilities/app_constants.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
import 'package:gallery_saver/gallery_saver.dart';
class FormCameraField extends StatefulWidget {
final InputDecoration decorations;
final Map field;
// functions
final Function onSaved;
final Function onFieldSubmitted;
FormCameraField({
this.decorations,
#required this.field,
#required this.onSaved,
#required this.onFieldSubmitted,
});
#override
_FormCameraFieldState createState() => _FormCameraFieldState();
}
class _FormCameraFieldState extends State<FormCameraField> {
List<File> images = [];
Future<void> _takePhoto(ImageSource source) async {
ImagePicker.pickImage(source: source, imageQuality: 90).then(
(File recordedImage) async {
if (recordedImage != null && recordedImage.path != null) {
try {
// store image to device gallery
if (widget.field["StoreCaptureToDevice"] == true) {
GallerySaver.saveImage(recordedImage.path,
albumName: kAppName.replaceAll(" ", "_"));
}
setState(() {
images.add(recordedImage);
});
} catch (e) {
print("ERROR SAVING THE FILE TO GALLERY");
print(e);
}
}
},
);
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: MaterialButton(
child: Text("Take Photo"),
onPressed: () async {
await _takePhoto(ImageSource.camera);
},
),
),
Expanded(
child: MaterialButton(
child: Text("Select Photo"),
onPressed: () async {
await _takePhoto(ImageSource.gallery);
},
),
),
],
),
ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: images.length,
itemBuilder: (BuildContext context, index) {
return Container(
child: Image.file(
images[index],
),
);
},
)
],
);
}
}
I faced the same problem.
This is because some images cause the crash of Flutter engine.
The final issue is here https://github.com/flutter/flutter/issues/73767
The example image that always causes crash on ios is here
https://github.com/flutter/flutter/issues/73932
So, waiting for the Flutter team fixes the bug.
Having a similiar problem. Replacing my images with smaller (~50kb) ones seems to be working fine so i think you are right, loading all those images on less powerfull devices seems to be the problem. There is an image package on pub.dev that should do the trick. I am using Firebase so i will lean more towards their new resize image extension. Goodluck and let us know if it works
I have the same problem. It's actually a bug in flutter. They are currently working to fix the bug in the next stable releases.
I created thumbnails, this worked for me.

How to jump between controls with 'tab' in flutter for web?

I want user to be able to jump between controls with 'Tab' in my flutter web app.
I followed this post to catch the key "Tab" and to navigate to next controls.
When user presses 'Tab', cursor jumps to the next text box, but then, when user types, no letters appears in the text box.
What can be wrong?
Here is the code:
class _LoginScreenState extends State<LoginScreen> {
FocusNode _passwordFocus;
FocusNode _emailFocus;
#override
void initState() {
super.initState();
_emailFocus = FocusNode();
_passwordFocus = FocusNode();
}
#override
void dispose() {
_emailFocus.dispose();
_passwordFocus.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final TextEditingController emailController =
new TextEditingController(text: this._email);
final TextEditingController passwordController =
new TextEditingController();
return Scaffold(
appBar: AppBar(
title: Text('Sign In'),
),
body: Column(
children: <Widget>[
RawKeyboardListener(
child: TextField(
autofocus: true,
controller: emailController,
decoration: InputDecoration(
labelText: "EMail",
),
),
onKey: (dynamic key) {
if (key.data.keyCode == 9) {
FocusScope.of(context).requestFocus(_passwordFocus);
}
},
focusNode: _emailFocus,
),
TextField(
controller: passwordController,
obscureText: true,
focusNode: _passwordFocus,
decoration: InputDecoration(
labelText: "Password",
),
),
],
),
);
}
}
It turned out the browser was taking the focus to other place.
I added prevention of default browser behavior to the method 'build':
import 'dart:html';
...
#override
Widget build(BuildContext context) {
document.addEventListener('keydown', (dynamic event) {
if (event.code == 'Tab') {
event.preventDefault();
}
});
...
The solution that worked for me is a little different.
I am on Flutter 2.0.1 with Dart 2.12.0
import 'dart:html';
import 'package:flutter/foundation.dart';
...
#override
Widget build(BuildContext context) {
if (kIsWeb) {
document.addEventListener('keydown',
(event) => {if (event.type == 'tab') event.preventDefault()});
}
...
}
...

parallax effect | scrollable background image in flutter

I'm trying to implement a scrollable background image (parallax).
Like in a home screen launcher.
An example:
In Evie launcher:
this video
I've tried using AnimatedBuilder mentioned here in the docs like this.
I'm using a ValueNotifier<double> as the listener for the animation of the AnimatedBuilder Widget.
The complete code is this
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'PageView Scrolling',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>{
ValueNotifier<double> _notifier;
double _prevnotifier;
double getOffset(){
if (_notifier.value == 0 && _prevnotifier != null){
return _prevnotifier;
}
return _notifier.value;
}
#override
void dispose() {
_notifier?.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
_notifier = ValueNotifier<double>(0);
_prevnotifier = _notifier.value;
_notifier.addListener(
(){
print('object ${_notifier.value}');
if (_notifier.value != 0)
_prevnotifier = _notifier.value;
}
);
}
#override
Widget build(BuildContext context) {
print("Size is ${MediaQuery.of(context).size}");
return Scaffold(
body: Stack(
children: <Widget>[
AnimatedBuilder(
animation: _notifier,
builder: (context, _) {
return Transform.translate(
offset: Offset(-getOffset() * 60, 0),
child: Image.network(
"https://w.wallhaven.cc/full/r2/wallhaven-r276qj.png",
height: MediaQuery.of(context).size.height,
fit: BoxFit.fitHeight
),
);
},
),
NotifyingPageView(
notifier: _notifier,
),
],
),
);
}
}
class NotifyingPageView extends StatefulWidget {
final ValueNotifier<double> notifier;
const NotifyingPageView({Key key, this.notifier}) : super(key: key);
#override
_NotifyingPageViewState createState() => _NotifyingPageViewState();
}
class _NotifyingPageViewState extends State<NotifyingPageView> {
int _previousPage;
PageController _pageController;
void _onScroll() {
// Consider the page changed when the end of the scroll is reached
// Using onPageChanged callback from PageView causes the page to change when
// the half of the next card hits the center of the viewport, which is not
// what I want
if (_pageController.page.toInt() == _pageController.page) {
_previousPage = _pageController.page.toInt();
}
widget.notifier?.value = _pageController.page - _previousPage;
}
#override
void initState() {
_pageController = PageController(
initialPage: 0,
viewportFraction: 0.9,
)..addListener(_onScroll);
_previousPage = _pageController.initialPage;
super.initState();
}
List<Widget> _pages = List.generate(
10,
(index) {
return Container(
height: 10,
alignment: Alignment.center,
color: Colors.transparent,
child: Text(
"Card number $index",
style: TextStyle(
color: Colors.teal,
fontWeight: FontWeight.bold,
fontSize: 25,
),
),
);
},
);
#override
Widget build(BuildContext context) {
return PageView(
children: _pages,
controller: _pageController,
);
}
}
The image can be found here
Now I have two issues:
The image when using fit: BoxFit.fitHeight is not overflowing fully. It's currently like this
Because the value will become zero when the animation is done it's snapping like this:
this video
I tried storing the value just before the _notifier.value becomes zero and use it when it returns zero but it resulted in that weird transition that I've shown you in that above video.
What do you suggest can be done to make something like a scrollable wallpaper in flutter?
Something like this
Design
This is not as trivial as I thought it would be.
TLDR; Github read the comments.
I used a ValueNotifier<double> like I mentioned to control the scroll.
Then instead of Transform.translate I used an OverflowBox with its alignment property. Which is computed based on the notifier.value before rendering.
And to display the image in fullscreen mode:
I used AspectRatio with a child DecoratedBox whose decoration is a BoxDecoration with its image as an ImageProvider.
All the code can be found here on github. (Read the comments)
And this issue on github has slightly detailed info and a less complicated alternate implementation by Antonello Galipò

Cannot Find Method 'SetState' within widget

I have a widget that is called in my main scaffolding file later. This widget contains a dropdown menu but, I cannot change the state when selecting another value. The field does not update and I get the error message 'error: Method not found: 'setState'.
setState(() {'
^^^^^^^^
I have updated the setState method and removed code from it but, it still says the method not found.
child: DropdownButton(
hint: Text('Medical'),
value: _selectedCustomerType,
onChanged: (newValue) {
setState(() {
_selectedCustomerType = newValue;
});
},
items: _customerType.map((cusType) {
print(cusType);
return DropdownMenuItem(
child: Text(cusType),
value: cusType,
);
}).toList(),
),
I need to be able to update the value and display it when the new value is chosen.
You can't use setState outside of a StatefulWidget so you should wrap your DropdownButton in a StatefulWidget, for example:
class StatefulDropdownButton extends StatefulWidget {
final List<String> _customerType;
StatefulDropdownButton(this._customerType);
#override
State<StatefulWidget> createState() => DropdownButtonState();
}
class DropdownButtonState extends State<StatefulDropdownButton> {
String _selectedCustomerType;
#override
Widget build(BuildContext context) {
return DropdownButton(
hint: Text('Medical'),
value: _selectedCustomerType,
onChanged: (newValue) {
setState(() {
_selectedCustomerType = newValue;
});
},
items: widget._customerType.map((cusType) {
print(cusType);
return DropdownMenuItem(
child: Text(cusType),
value: cusType,
);
}).toList(),
);
}
}
SetState is not accessible inside the main method, and neither inside function, to make it accessible , you need to create a Stateful class and exactly in the State class , because actually your widget is a statefull class : it changes its state everytime the user make an event..

Flutter: bloc, how to show an alert dialog

I´m new in the bloc pattern and stream stuff. I want to show up an alert dialog when I press a button, but I can´t find a way to do it. Actually my code is:
Widget button() {
return RaisedButton(
child: Text('Show alert'),
color: Colors.blue[700],
textColor: Colors.white,
onPressed: () {
bloc.submit();
});
}
return Scaffold(
appBar: AppBar(
title: Text("Title"),
),
body: StreamBuilder(
stream: bloc.getAlert,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text("I have Dataaaaaa ${snapshot.data}");
} else
return ListView(
children: <Widget>[
Container(
button()
)
...
And the BLoC:
final _submissionController = StreamController();
Stream get submissionStream=> _submissionController.stream;
Sink get submissionSink=> _submissionController.sink;
I tried to do something like:
Widget button() {
return StreamBuilder(
stream: submissionStream
builder: (context, snapshot){
if (snapshot.hasData){
return showDialog(...)
}else
return RaisedButton(
child: Text('Show alert'),
color: Colors.blue[700],
textColor: Colors.white,
onPressed: () {
bloc.submit();
});
}
But, of course, it didn´t work.
You can't show a dialog when build working. When you have new data, then you create a new widget. Probably better for you will be not using the stream in this case, but if it necessary you should use
WidgetsBinding.instance.addPostFrameCallback((_) => yourFunction(context));
or
Future.microtask(() => showDialogFunction(context));
in your if
if (snapshot.hasData) {
WidgetsBinding.instance.addPostFrameCallback((_) => showDialogFunction(context));
}
This code will be launched after build method, so dialog will show immediately.
Bloc function always return widget, so always return button() or different wiget when stream has data
You can use BlocListener for showing Dialogs, Snackbars or for navigating to a new page.
With this approach you may want to refactor to rely on the bloc state rather than accessing the stream directly.
Listener is guaranteed to only be called once for each state change, however builder can be called many times. Also you can't do some operations on builders, such as navigating to another page.
return Scaffold(
appBar: AppBar(
title: Text("Title"),
),
body: BlocProvider<YourBloc>(
create: () => YourBloc(),
child: Stack([
SnackbarManager(),
YourScreen(),
]),
),
);
...
/// This is basically an empty UI widget that only
/// manages the snackbar
class SnackbarManager extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocListener<YourBloc, YourBlocState>(
listener: (context, state) {
if (state.hasMyData) {
Scaffold.of(context).showSnackBar(SnackBar(
content:
Text("I got data"),
));
}
},
child: Container(),
);
}
}
I know I'm late to the party, but maybe this will help someone.
I'm currently learning about BLoC myself and ran into a similar problem.
First of all, I want to recommend the flutter_bloc package from pub.dev.
It contains Widgets that help you with this like BlocListener and BlocConsumer.
If you want to go without it, you could try using a StatefulWidget and listen to it separately and use your logic to show the dialog. (also make sure your stream is broadcasting as in my example, so it can have multiple listeners)
I've made an example which you could copy-past into dartpad.dev/flutter:
import 'dart:async';
import 'package:flutter/material.dart';
final myStream = StreamController<bool>.broadcast();
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
initState() {
super.initState();
myStream.stream.listen((show){
if(show)
showDialog(
barrierDismissible: false,
context: context,
builder: (context) {
return AlertDialog(
title: Text('MyDialog'),
actions: [
TextButton(
child: Text('Close'),
onPressed: (){
myStream.sink.add(false);
}),
]
);
}
);
if(!show) {
Navigator.pop(context);
}
});
}
#override
Widget build(BuildContext context) {
return Center(child: ElevatedButton(
child: Text('Show Alert'),
onPressed: (){
myStream.sink.add(true);
}));
}
}
Here is what I did, it might be wrong as I'm also new to flutter. But works for my scenario.
Widget build(BuildContext context) {
final authBloc = BlocProvider.of<AuthBloc>(context);
authBloc.outServerResponse.listen((serverResponse) {
if (serverResponse.status == 'success') {
_navigateToLogin();
} else {
_showSnakBar(serverResponse.message);
}
});
.... Rest of the code which returns the widget,
which in my case is form widget with button for submitting as follows,
onPressed: () {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
authBloc.processRegister.add(_registrationData.toMap());
}
}
outServerResponse is the stream that outputs after finishing API POST call.
authBloc.processRegister is the input sink to pass form data to my Auth API Service Provider.
_nagivateToLogin & _showSnakBar are simple functions
_navigateToLogin() {
Navigator.of(context).pop();
}
_showSnakBar(String msg) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(msg),
),
);
}
this process working for me.
I called my Dialog before return the widget
Future.microtask(() => showLoginSuccess(BuildContext context));
If you're using flutter_bloc package which I suggest to use, you should use the provided BlocListener widget which listens to state changes and could execute logic codes. like this for example:
BlocListener<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container(),
);
but if you also need the build widget, you should use BlocConsumer widget which has the listener and the builder at the same time:
BlocConsumer<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
);
It's common to show a dialog without changing the build widget,
so BlocConsumer offers the buildWhen option for this situation which takes the previous and current states to decide about the builder:
buildWhen: (state, currentState){
if (state is MainComplexTableState && currentState is NewComplexRegistration) {
return false;
}
if (state is ErrorToShowUp) {
return false;
}
return true;
},
I solved it by maintaining two context as follows
**
BlocProvider of type A ==>widget class B(showdialog(context:context,builder(context2){
Blocprvider.value(value:Blocprovider.of<A>.context)
child:BlocListener(
listner(context2,state)
{//
your works
//}
child:AlertDialog( some widgets
a button function ()=> context.read<A>().function or property name
//
1.here we call old context in fact it is registered with provider, 2. context2 is only for building a new builder widget.
3.hence we get bloc passed through a navigation and accessible in navigated alert widget without creating it

Resources