Found a problem regarding Flutter's handling towards l10n and void initState().
l10n works well pm it's own. Does the job but I found a particular scenario which I thought should be labelled.
I'll be more specific by showing an example.
The following code has been taken from the gallery app:
...
#override
void initState() {
super.initState();
_navigationViews = <NavigationIconView>[
NavigationIconView(
icon: const Icon(Icons.access_alarm),
title: 'Alarm',
color: Colors.deepPurple,
vsync: this,
),
NavigationIconView(
activeIcon: CustomIcon(),
icon: CustomInactiveIcon(),
title: 'Box',
color: Colors.deepOrange,
vsync: this,
),
NavigationIconView(
activeIcon: const Icon(Icons.cloud),
icon: const Icon(Icons.cloud_queue),
title: 'Cloud',
color: Colors.teal,
vsync: this,
),
NavigationIconView(
activeIcon: const Icon(Icons.favorite),
icon: const Icon(Icons.favorite_border),
title: 'Favorites',
color: Colors.indigo,
vsync: this,
),
NavigationIconView(
icon: const Icon(Icons.event_available),
title: 'Event',
color: Colors.pink,
vsync: this,
)
];
_navigationViews[_currentIndex].controller.value = 1.0;
}
...
I'm struggling using localisation within the initState function.
You can (somewhat) bypass this by using the user's language code and translate it manually instead of using a locales.dart file, neither less I assume this is an ongoing problem that should be addressed.
NOTE: As I was writing this post I've thought about an idea to bypass this by calling the l10n strings from a former class - testing this theory.
So I ended up "passing down" the values from a former class. This "problem" can be resolved by using ether InheritedWidget or "passing down values".
Related
Hi I am new to flutter and have been going through flutter's udacity course building their unit converter app to try to learn about the framework. I was attempting to architecture the app using bloc but have ran into an issue with my dropdown menu. Every time when I change the item in the dropdown it resets back to the default value when focusing on the text input field. It looks like the widget tree i rebuilt when focusing on a textfield. The default units are the reset because in my bloc constructor I have a method to set default units. I am at a loss for where I would move my default units method so that it does not conflict. What should I do in my bloc to set default units only when a distinct category is set, and when it is first being built.
I tried using _currentCatController.stream.distinct method to only update the stream when distinct data is passed but that did not seem to work either. I tried to wrap the default units method in various conditional statements that did not give me the result I wanted.
you can find all the source here https://github.com/Renzo-Olivares/Units_Flutter
class _ConverterScreenState extends State<ConverterScreen> {
///function that creates dropdown widget
Widget _buildDropdown(
bool selectionType, ValueChanged<dynamic> changeFunction) {
print("build dropdown");
return Padding(
padding: const EdgeInsets.symmetric(vertical: 15.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black, style: BorderStyle.solid),
borderRadius: BorderRadius.circular(4.0)),
child: DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: StreamBuilder<Unit>(
stream: _conversionBloc.inputUnit,
initialData: widget._category.units[0],
builder: (context, snapshotIn) {
return StreamBuilder<Unit>(
stream: _conversionBloc.outputUnit,
initialData: widget._category.units[1],
builder: (context, snapshotOut) {
return StreamBuilder<Category>(
stream: _conversionBloc.currentCategory,
initialData: widget._category,
builder: (context, snapshotDropdown) {
return DropdownButton(
items: snapshotDropdown.data.units
.map(_buildDropdownItem)
.toList(),
value: selectionType
? snapshotIn.data.name
: snapshotOut.data.name,
onChanged: changeFunction,
isExpanded: true,
hint: Text("Select Units",
style: TextStyle(
color: Colors.black,
)),
);
});
});
})),
),
),
);
}
}
class ConversionBloc {
//input
final _currentCatController = StreamController<Category>();
Sink<Category> get currentCat => _currentCatController.sink;
final _currentCatSubject = BehaviorSubject<Category>();
Stream<Category> get currentCategory => _currentCatSubject.stream;
ConversionBloc() {
print("conversion bloc");
//category
_currentCatController.stream.listen((category) {
print("setting category ${category.name}");
_category = category;
_currentCatSubject.sink.add(_category);
//units default
setDefaultUnits(_category);
});
}
void setDefaultUnits(Category category) {
print("setting default units for ${category.name}");
_inputUnits = category.units[0];
_outputUnits = category.units[1];
_inputUnitSubject.sink.add(_inputUnits);
_outputUnitSubject.add(_outputUnits);
}
}
The issue here is that the DropdownButton value wasn't updated on onChanged. What you can do here is handle the value passed from onChanged and update the DropdownButton value. Also, focusing on a Widget displayed on screen shouldn't rebuild Widget build().
how do I test BottomNavigationBarItems via FlutterDriver?
FlutterDriver allows accessing Widgets via text, byValueKey, byTooltip and byType.
But none of these methods work out for my App because of following reasons:
text: The App is localized and I need to test the App in multiple languages.
byValueKey: BottomNavigationBarItems do not have key properties.
byTooltip: BottomNavigationBarItems do not have toolTip properties.
byType: byType only returns the first match of the type and no list (needed because I have multiple tabs).
Thank you very much!
Cheers.
Not sure if you have found answer for this question, but I am going to post a solution here which works for me. Basically, BottomNavigationBar has a key property which you need to use. Once Flutter Driver identifies this key, then you can tell driver to tap on any of its child items ie BottomNavigationBarItem.
My screen has 2 bottomNavigationBarItems as shown below and I defined key for their parent widget ie BottomNavigationBar as:
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.shifting,
key: Key('bottom'),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit, color: Colors.green,),
title: Text('First', style: TextStyle(color: Colors.black),)
),
BottomNavigationBarItem(
icon: Icon(Icons.cast, color: Colors.yellow,),
title: Text('Second', style: TextStyle(color: Colors.black),)
)
],
),
And I wrote a flutter driver test to tap on both items which worked perfectly.
test('bottomnavigationbar test', () async {
await driver.waitFor(find.byValueKey('bottom'));
await driver.tap(find.text('First'));
print('clicked on first');
await driver.tap(find.text('Second'));
print('clicked on second too');
});
Result:
As mentioned by #bsr and #user12563357- you can use the key on Text widget:
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.shifting,
key: Key('bottom'),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit),
title: Text('First', key: Key('first'),)
),
BottomNavigationBarItem(
icon: Icon(Icons.cast),
title: Text('Second', key: Key('second'),)
)
],
),
and find the text to click on the bar item in test:
final firstItem = find.byValueKey('first');
await driver.tap(firstItem);
btw: you can also find BottomNavigationBarItem using find.ancestor
find.ancestor(of: firstItem, matching: find.byType("BottomNavigationBarItem"));
but you can't tap on it.
You have two options - byIcon or with text by getting the localization from the context.
You can get the state of any kind of StatefulWidget.
final MyWidgetState state = tester.state(find.byType(MyWidget));
with this you can get the context and with that the current localizaion.
final l10n = AppLocalizations.of(state.context);
await tester.tap(find.text(l10n.youTitle));
One other option is to get the widget by Icon:
await tester.tap(find.byIcon(Icons.your_icon));
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.shifting,
key: Key(`bottom`),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit, color: Colors.green,),
title: InkWell(child:Text(`First`, style: TextStyle(color: Colors.black),key:Key(`First`)),)
),
BottomNavigationBarItem(
icon: Icon(Icons.cast, color: Colors.yellow,),
title: InkWell(child:Text(`Second`, style: TextStyle(color: Colors.black),key:Key(`Second`)),)
)
],
),
test(`bottomnavigationbar test`, () async {
await driver.waitFor(find.byValueKey(`bottom`));
await driver.tap(find.byValueKey(`First`));
print('clicked on first');
await driver.tap(find.byValueKey(`Second`));
print('clicked on second too');
});
i have custom button and i want to add method in my onpressed from my parameter,unfortunately i'm still stuck what type should i use in my custom button parameter, curently i hard code the onpressed method and pass a string route to navigate the problem is not all my button is made to navigate there is one button to logout, i already used void in my parameter but it's still doesn't work.
here is my custom button
FlatButton buttonMenu(String title, IconData icon, String route) {
return new FlatButton(
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(25.0)),
color: Colors.blueGrey,
child: new Container(
height: 50.0,
width: 120.0,
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Padding(
padding: EdgeInsets.only(right: 10.0),
child: new Icon(
icon,
color: Colors.white70,
size: 30.0,
),
),
new Text(
title,
style: new TextStyle(fontSize: 20.0, fontWeight: FontWeight.w300),
),
],
),
),
onPressed: () {
Navigator.of(context).pushNamed('/$route');
},
);
}
}
and here is how i call the custom button
buttonMenu('MVP Arc', Icons.star, 'EmployeeScreen')
and what i want is i can send method in my parameter to my onpressed so it will be like this
buttonMenu('MVP Arc', Icons.star, myMethod())
my method for navigate
void _navigate(String route){
Navigator.of(context).pushNamed('/$route');
}
the onPressed type is VoidCallback so make your parameter like this this type.
FlatButton buttonMenu(String title, IconData icon, VoidCallback onCustomButtonPressed )
and when you call it pass a reference to it
buttonMenu('MVP Arc', Icons.star, myMethod)
and don't forget to do the same in your function body
onPressed: onCustomButtonPressed ,
UPDATE:
if you want to pass parameters you have to declare your own Signature :
Example in your case :
typedef VoidNavigate = void Function(String route);
and then use this new type
FlatButton buttonMenu(String title, IconData icon, VoidNavigate onCustomButtonPressed)
call it like this
onPressed: (){onCustomButtonPressed},
and you pass your function like this
buttonMenu('MVP Arc', Icons.star, _navigate('EmployeeScreen'))
or you can just do it like this as #Kirill Shashov said
buttonMenu('MVP Arc', Icons.star, () {_navigate('EmployeeScreen');})
The accepted answer for passing parameters no longer seems to work on newer versions of Flutter.
In case someone searches for a similar issue, the modifications below worked for me.
Modifying Raouf Rahiche's answer, the working method as of 2019. October 28 is as follows.
Update: added mising flutter version number below
Flutter version: 1.9.1+hotfix.5
Dart version: 2.5.0
Using the existing typedef:
typedef VoidNavigate = void Function(String route);
Using the type changed in a small way, you have to pass the parameter to the button separately.
FlatButton buttonMenu(
String title,
IconData icon,
VoidNavigate onCustomButtonPressed,
String route //New addition
) {
...
}
Calling it now:
onPressed: () { onCustomButtonPressed(route); },
Passing the function:
buttonMenu('MVP Arc', Icons.star, _navigate, 'EmployeeScreen')
Here is what worked for me:
Flutter Version : 2.6.0
Dart Version : 2.15.0
in my customButton function:
Widget customButton(VoidCallback onPressed)
{
....
onPressed : onPressed.call(),
}
and no other changes needed.
the only we need to do is add .call() to the end of the function to be void.
onPressed: ()=>onPressed to onPressed: ()=>onPressed.call()
Background: I am working on a mobile application in which I use a WebViewScaffold to load an online directory. This particular directory provides a guided tour on initial visit.
Problem: Each time I navigate to the directory WebView, the tour starts from the beginning (which freezes the user until the tour is finished). How might I keep this from happening? When I open the directory in a browser, the status of the tour is saved in the browser's local storage variables. Is there a way to save or reset the local storage variables in flutter?
Code: Upon button click, I push a new route where I create a new Directory object which is shown below:
class MobileDirectory extends StatelessWidget {
final _mobileDirectory = 'my.mobileDirectory.url';
final _directoryTitle = new Text(
'Directory',
style: const TextStyle(
fontSize: 17.0, color: Colors.white, fontWeight: FontWeight.w400),
);
final _backgroundColor = new Color.fromRGBO(29, 140, 186, 1.0);
final _backButton = new BackButton(color: Colors.white);
final _padding = new EdgeInsets.only(left: 2.0);
final _imageAsset = new Image.asset('assets/appBar.jpg');
#override
Widget build(BuildContext context) {
return new WebviewScaffold(
appBar: new AppBar(
leading: _backButton,
centerTitle: true,
backgroundColor: _backgroundColor,
title: new Padding(
padding: _padding,
child: _directoryTitle,
),
actions: <Widget>[
_imageAsset,
],
),
url: _mobileDirectory,
);
}
}
Note: Please let me know if more information should be provided.
The most recent version 0.1.4 supports using localstorage
https://github.com/dart-flitter/flutter_webview_plugin/blob/9873b2d9a2167f76018d4f6e7fc6e2a1ed8ce5c9/lib/src/webview_scaffold.dart#L35
For the last few days, I've been reading through flutter framework documentation and especially the sliver part but I'm not quite sure where to start.
I'm trying to implement the sticky headers and snap effect.
Might the RenderSliverList be a good start? Do I need to re-layout things? Do I need to do additional drawing? And if so where?
Any help on where to start would be a huge help, thanks in advance!
Edit: I think I understood the layout part now, but I just can't find where the painting is supposed to happen.
Edit 2: For clarification, this is the desired "sticky header effect":
How can I make sticky headers in RecyclerView? (Without external lib)
and this is the "snap" effect:
https://rubensousa.github.io/2016/08/recyclerviewsnap
For the "sticky header effect" I ran into this problem myself, so I created this package to manage sticky headers with slivers: https://github.com/letsar/flutter_sticky_header
To use it you have to create one SliverStickyHeader per section in a CustomScrollView.
One section can be wrote like this:
new SliverStickyHeader(
header: new Container(
height: 60.0,
color: Colors.lightBlue,
padding: EdgeInsets.symmetric(horizontal: 16.0),
alignment: Alignment.centerLeft,
child: new Text(
'Header #0',
style: const TextStyle(color: Colors.white),
),
),
sliver: new SliverList(
delegate: new SliverChildBuilderDelegate(
(context, i) => new ListTile(
leading: new CircleAvatar(
child: new Text('0'),
),
title: new Text('List tile #$i'),
),
childCount: 4,
),
),
);
If you want, the entire source code for the above demo is here: https://github.com/letsar/flutter_sticky_header/blob/master/example/lib/main.dart
I hope this will help you.
It's dead simple :
Use a CustomScrollView and give it as child both a SliverList and a SliverAppBar. You may replace the SliverList with a SliverGrid if you need to.
Then, depending on the effect you want to achieve, there are a few properties you may set on SliverAppBar:
snap
expandedHeight (+ flexibleSpace)
floating
pinned
In the end, you may have something similar to :
new CustomScrollView(
slivers: <Widget>[
new SliverAppBar(
title: new Text("Title"),
snap: true,
floating: true,
),
new SliverFixedExtentList(
itemExtent: 50.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
return new Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: new Text('list item $index'),
);
},
),
),
],
)
Even better, you can concatenate different scroll behaviour inside a single CustomScrollView.
Which means you can potentially have a grid followed by a list just by adding a SliverGrid as a child to your scrollView.
I know I know, flutter is awesome.
I managed to do the stickyheader effect on Flutter for an iOS app using the following code - credit goes to this piece of code written here from where I drew my inspiration (https://github.com/flutter/flutter/blob/master/examples/flutter_gallery/lib/demo/animation/home.dart#L112):
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate({
#required this.collapsedHeight,
#required this.expandedHeight,}
);
final double expandedHeight;
final double collapsedHeight;
#override double get minExtent => collapsedHeight;
#override double get maxExtent => math.max(expandedHeight, minExtent);
#override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return new Container(color: Colors.red,
child: new Padding(
padding: const EdgeInsets.only(
left: 8.0, top: 8.0, bottom: 8.0, right: 8.0),
child: new Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Text("Time"), new Text("Price"), new Text("Hotness")
],
),
)
);
}
#override
bool shouldRebuild(#checked _SliverAppBarDelegate oldDelegate) {
return expandedHeight != oldDelegate.expandedHeight
|| collapsedHeight != oldDelegate.collapsedHeight;
}
}
To make it sticky, add the _SliverAppBarDelegate to the silvers widget list:
new SliverPersistentHeader(delegate: new _SliverAppBarDelegate(collapsedHeight: 36.0, expandedHeight: 36.0), pinned: true, ),
I'm not really sure how to make the _SliverAppBarDelegate wrap the content though, I had to provide it with a size of 36 logical pixels to get it to work. If anyone know how it could just wrap content, please drop a comment to the answer below.
I solved this problem, try sticky_and_expandable_list.
Features
Support build an expandable ListView, which can expand/collapse section or create sticky section header.
Use it with CustomScrollView、SliverAppBar.
Listen the scroll offset of current sticky header, current sticky header index and switching header index.
Only use one list widget, so it supports large data and a small memory usage.
More section customization support, you can return a new section widget by sectionBuilder, to customize background,expand/collapse animation, section layout, and so on.
Support add divider.
As per documentation, you can place a StickyHeader or StickyHeaderBuilder inside any scrollable content.
The documentation page provides only an example how to apply a StickyHeader with a ListView, ok what about the other widgets such as SingleChildScrollView or CustomScrollView ?
In this example I will provide a very simple StickyHeader with a SingleChildScrollView and sure you can put it inside any SingleChildScrollView you want:
return Container(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("My title"),
StickyHeader(
header: Container(
child: // Put here whatever widget you like as the sticky widget
),
content: Column(
children: [
Container(
child: // Put here whatever widget you like as scrolling content (Column, Text, ListView, etc...)
),
],
),
),
],
),
),
);
without implementing yours using sliver, you can achieve this using flutter community awesome plugin.
https://pub.dev/packages/sticky_headers