Related
AppBar has a search icon, when clicked it displays a TextField in the 'title' property. When search icon is clicked I need TextField to autofocus. Problem is with 'autofocus' property, if set to true, instead of only changing the state of the title property, something is causing the widget to turn into a 'dirty' state. This causes the main build function to get called which rebuilds the entire thing.
Tried to replicate this and provide a sample app but strangely enough it seems to work just fine in the demo.
Any debug suggestions?
AppBar(
centerTitle: true,
title: StreamBuilder(
stream: false,
initialData: symbolBloc.isSearching,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot){
if(snapshot.data) {
return TextField(
// autofocus: true, <--- here
controller: searchQuery,
style: new TextStyle(
color: Colors.white,
),
);
}
return new Text("", style: new TextStyle(color: Colors.white)
);
}),
backgroundColor: Colors.blueGrey,
elevation: 0.0,
actions: <Widget>[
StreamBuilder(
stream: bloc.isSearchActive,
initialData: false,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot){
if(snapshot.data)
return activeSearchIconButton(symbolBloc);
return searchIconButton(symbolBloc);
},
),
],
),
searchIconButton(SymbolBloc bloc){
return new IconButton(
icon: new Icon(
Icons.search,
color: Colors.white,
),
tooltip: 'Search',
onPressed: (){
bloc.displaySearchField(true);
},
);
}
activeSearchIconButton(SymbolBloc bloc){
return new IconButton(
icon: new Icon(
Icons.close,
color: Colors.white,
),
tooltip: 'Search',
onPressed: (){
bloc.displaySearchField(false);
},
);
}
You can set FocusNode property of TextField and on Button click call FocusScope.of(context).requestFocus(_focusNode);
The problem had do with InheritedWidgets. Having more than one InheritedWidget was the cause. I had a parent widget wrapping the runApp() function (holds a reference to api object) and a child widget wrapping each route (holds reference to bloc class for each specific screen).
Problem went away once I moved the contents of child widget over to the parent and removed the child InheritedWidget altogether.
I was trying to display a iOS themed dialog box in my Flutter app, but I was unable to find anything in the docs
The keyword for Android theme/style is Material (default design), the keyword for iOS theme/style is Cupertino. Every iOS theme widget has the prefix Cupertino. So that, for you requirement, we can guess the keyword is CupertinoDialog/CupertinoAlertDialog
You can refer here for all of them https://flutter.io/docs/reference/widgets/cupertino
new CupertinoAlertDialog(
title: new Text("Dialog Title"),
content: new Text("This is my content"),
actions: <Widget>[
CupertinoDialogAction(
isDefaultAction: true,
child: Text("Yes"),
),
CupertinoDialogAction(
child: Text("No"),
)
],
)
First you check if platForm ios or android .. then return widget for the current device ..
Future<bool> showAlertDialog({
#required BuildContext context,
#required String title,
#required String content,
String cancelActionText,
#required String defaultActionText,
}) async {
if (!Platform.isIOS) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text(content),
actions: <Widget>[
if (cancelActionText != null)
FlatButton(
child: Text(cancelActionText),
onPressed: () => Navigator.of(context).pop(false),
),
FlatButton(
child: Text(defaultActionText),
onPressed: () => Navigator.of(context).pop(true),
),
],
),
);
}
// todo : showDialog for ios
return showCupertinoDialog(
context: context,
builder: (context) => CupertinoAlertDialog(
title: Text(title),
content: Text(content),
actions: <Widget>[
if (cancelActionText != null)
CupertinoDialogAction(
child: Text(cancelActionText),
onPressed: () => Navigator.of(context).pop(false),
),
CupertinoDialogAction(
child: Text(defaultActionText),
onPressed: () => Navigator.of(context).pop(true),
),
],
),
);
}
I used CupertinoAlertDialog inside the ShowDialog, you can find the same below
showDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: new Text("Dialog Title"),
content: new Text("This is my content"),
actions: <Widget>[
CupertinoDialogAction(
isDefaultAction: true,
child: Text(StringConstants.BIOMETRICAUTHORIZED),
),
CupertinoDialogAction(
child: Text("No"),
)
],
)
);
This will be a lot of explaining but i hope someone will be able to help.
Currently i have search button on my appbar that, when pressed, covers over my appbar title with a textfield
The normal appbar title is an image and i am adding functionality that when pressed, it brings you to the home screen. This is were it gets tricky, because i need to use this line of code to accomplish just that
new InkWell (
child: Image.asset(
'images/logoGrey.png',
fit: BoxFit.fill,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
LandingPage(),
),
);
},
);
so i set that to a variable like so
class _ControlsPageState extends State<ControlsPage> {
Widget appBarTitle = new InkWell (
child: Image.asset(
'images/logoGrey.png',
fit: BoxFit.fill,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
LandingPage(),
),
);
},
);
The reason i have this variable is so that i can change the state of the appbar(title) to a textfield when i click on the search button and back to the image when i close out.
but this wont work (error on "context") seeing as though this line of code below can only be used under "Widget build(BuildContext context)" and not in my class....
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
LandingPage(),
),
);
},
The bottom line is i need my appbar title to be a callback to the variable "appBarTitle", and the variable gets an error on "context", is there anyway i can make this work?
here is the appbar code in case it helps
appBar: AppBar(
iconTheme: new IconThemeData(color: Theme.CompanyColors.coolGrey),
backgroundColor: Colors.white,
centerTitle: true,
title: appBarTitle ,
actions: <Widget>[
new IconButton(
icon: actionIcon,
onPressed: () {
setState(() {
if (this.actionIcon.icon == Icons.search) {
this.actionIcon =
new Icon(Icons.close, color: Theme.CompanyColors.coolGrey);
this.appBarTitle = new TextField(
onSubmitted: (String str) {
setState(() {
result = str;
});
controller.text = "";
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ControlSearchPage(
search: result, title: "${widget.title}"),
),
);
},
style: new TextStyle(
color: Colors.black,
),
decoration: new InputDecoration(
prefixIcon:
new Icon(Icons.search, color: Theme.CompanyColors.coolGrey),
hintText: "Search...",
hintStyle: new TextStyle(color: Theme.CompanyColors.coolGrey)),
);
} else {
this.actionIcon =
new Icon(Icons.search, color: Theme.CompanyColors.coolGrey);
this.appBarTitle = new InkWell (
child: Image.asset(
'images/logoGrey.png',
fit: BoxFit.fill,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
LandingPage(),
),
);
},
);
}
});
},
),
],
),
any comments will be appreciated
You should change appBarTitle to be a method that can generate the widget on state change rather than saving it to a variable. This way, you can ensure that it will only be generated when context is available.
// Define a bool to hold the current search state
bool _isSearching = false;
...
// In your build method
appBar: AppBar(
iconTheme: new IconThemeData(color: Theme.CompanyColors.coolGrey),
backgroundColor: Colors.white,
centerTitle: true,
title: _buildAppBarTitle(),
actions: <Widget>[
new IconButton(
icon: _isSearching
? new Icon(Icons.close, color: Theme.CompanyColors.coolGrey)
: new Icon(Icons.search, color: Theme.CompanyColors.coolGrey),
onPressed: () {
setState(() => _isSearching = !_isSearching);
},
),
],
),
...
// Define a separate method to build the appBarTitle
Widget _buildAppBarTitle() {
if (_isSearching) {
return new TextField(
onSubmitted: (String str) {
setState(() {
result = str;
});
controller.text = "";
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ControlSearchPage(
search: result, title: "${widget.title}"),
),
);
},
style: new TextStyle(
color: Colors.black,
),
decoration: new InputDecoration(
prefixIcon:
new Icon(Icons.search, color: Theme.CompanyColors.coolGrey),
hintText: "Search...",
hintStyle: new TextStyle(color: Theme.CompanyColors.coolGrey)),
);
} else {
return new InkWell (
child: Image.asset(
'images/logoGrey.png',
fit: BoxFit.fill,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
LandingPage(),
),
);
},
);
}
I want 2 tab pages with a ListView each to share a single RefreshIndicator. However, a RefreshIndicator must have Scrollable as a child (which a TabBarView isn't) so instead I tried making 2 RefreshIndicators per tab as shown in the code below.
But this brings a different problem, I also wanted a floating AppBar which meant I had to use a NestedScrollView. So as a result I end up triggering both RefreshIndicators' onRefresh method whenever I scroll down. Whereas I only need one to refresh.
import 'package:flutter/material.dart';
main() {
runApp(
MaterialApp(
home: DefaultTabController(
length: 2,
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
floating: true,
snap: true,
bottom: TabBar(
tabs: [
Tab(text: 'Page1'),
Tab(text: 'Page2'),
],
),
),
];
},
body: TabBarView(
children: [
Page(1),
Page(2),
],
),
),
),
),
),
);
}
class Page extends StatefulWidget {
final pageNumber;
Page(this.pageNumber);
createState() => PageState();
}
class PageState extends State<Page> with AutomaticKeepAliveClientMixin {
get wantKeepAlive => true;
build(context){
super.build(context);
return RefreshIndicator(
onRefresh: () => Future(() async {
print('Refreshing page no. ${widget.pageNumber}'); // This prints twice once both tabs have been opened
await Future.delayed(Duration(seconds: 5));
}),
child: ListView.builder(
itemBuilder: ((context, index){
return ListTile(
title: Text('Item $index')
);
}),
)
);
}
}
The AutomaticKeepAliveClientMixin is there to prevent the pages rebuilding every time I switch tabs as this would be an expensive process in my actual app.
A solution that uses a single RefreshIndicator for both tabs would be most ideal, but any help is appreciated.
DefaultTabController(
length: tabs.length,
child: RefreshIndicator(
notificationPredicate: (notification) {
// with NestedScrollView local(depth == 2) OverscrollNotification are not sent
return notification.depth == 2;
},
onRefresh: () => Future.value(null),
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
SliverAppBar(...)
];
},
body: TabBarView(
children: tabs,
),
),
),
)
Could wrap whole NestedScrollView with RefreshIndicator and update notificationPredicate:
DefaultTabController(
length: tabs.length,
child: RefreshIndicator(
notificationPredicate: (notification) {
// with NestedScrollView local(depth == 2) OverscrollNotification are not sent
if (notification is OverscrollNotification || Platform.isIOS) {
return notification.depth == 2;
}
return notification.depth == 0;
},
onRefresh: () => Future.value(null),
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
SliverAppBar(...)
];
},
body: TabBarView(
children: tabs,
),
),
),
)
If you want floating app bar then you have to use nested scroll view and sliver app bar . When you try to use refresh indicator in a list which a child of tab bar view , refresh indicator doesn't work. This is just because of the nested scroll view .
If you have suppose two lists as child of tab bar view, you want to refresh only one or both at a time then follow the below code.
Wrap the nested scroll view with refresh indicator then on refresh part ,
RefreshIndicator(
color: Colors.red,
displacement: 70,
onRefresh: _refreshGeneralList,
key: _refreshIndicatorKey,
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
centerTitle: true,
title: Text(
"App Bar",
style: TextStyle(
color: Colors.black,
fontSize: 14,
),
leading: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: IconButton(
icon: Icon(Icons.profile),
onPressed: () {
Navigator.of(context).pop();
},
),
),
actions: [
InkWell(
onTap: () {
setState(() {
isPremium = !isPremium;
});
},
child: Icon(
Icons.monetization_on,
color: isPremium ? Colors.green : Colors.blueGrey,
size: 33,
)),
SizedBox(
width: 25,
)
],
elevation: 0,
backgroundColor: Colors.white,
pinned: true,
floating: true,
forceElevated: innerBoxIsScrolled,
bottom: isPremium
? TabBar(
labelStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600),
labelColor: Colors.blueGrey,
indicatorColor:Colors.red,
unselectedLabelColor:
Colors.green,
labelPadding: EdgeInsets.symmetric(vertical: 13.5),
controller: _tabController,
tabs: [
Text(
"General",
),
Text(
"Visitors",
),
])
: null,
)
];
},
body: isPremium
? TabBarView(
controller: _tabController,
children: [
generalNotificationsList(context),
visitorsNotificationsList(context),
])
: generalNotificationsList(context),
),
),
add a function which calls a future. In the future part we will write the code if one child or two child of tab bar view will be scrolled.
Future _refreshGeneralList() async{
print('refreshing ');
GeneralNotificationBloc().add(LoadGeneralNotificationEvent(context));
PremiumNotificationBloc().add(LoadPremiumNotificationEvent(context));
return Future.delayed(Duration(seconds: 1));
}
i run the code in ios simulator ,but ios alaways loading,the webview state is startLoad,but canot finishLoad,
the page always loading,the code liek this:
#override
Widget build(BuildContext context) {
List<Widget> titleContent = [];
titleContent.add(new Text(
"资讯详情",
style: new TextStyle(color: Colors.white),
));
if (!loaded) {
titleContent.add(new CupertinoActivityIndicator());
}
titleContent.add(new Container(width: 50.0));
print(widget.id);
return new WebviewScaffold(
url: "http://www.baidu.com",
appBar: new AppBar(
title: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: titleContent,
),
actions: <Widget>[
new IconButton(
icon: icon,
onPressed: () {
print('收藏');
setState(() {
icon = new Icon(Icons.star);
});
},
),
new IconButton(
icon: new Icon(Icons.share),
onPressed: () {
print('分享');
},
),
],
iconTheme: new IconThemeData(color: Colors.white),
),
withZoom: false,
withLocalStorage: false,
withJavascript: true,
withLocalUrl: true,
);
}
the page like :
the page like this
thanks
Listen for the httpError stream. Maybe you're getting some errors, because normally it should work.
FlutterWebviewPlugin().onHttpError.listen((WebViewHttpError item) {
print(" WebView onHttpError.code: ${item.code}");
});
Try to remove this line:
withLocalUrl: true,