So I have a very simple application.
In Main I initialize Admob and call Root Widget.
From Root I show a Stateful Widget "Home".
From Home Call the Notification Logic.
When you "click" on the notification I send you again to Root.
When I first run the application, the adMobBanner works good.
After you receive the notification and you click on it, the app starts again, everything works as expected but the adMobBanner is a black banner now.
I think the Admob is not initialized properly. I tried to "Admob.initialize("appId")" in Root, in LocalNotification widget, everywhere, but still the same result.
Any ideas?
void main() async {
Admob.initialize("appId");
runApp(Root());
}
class Root extends StatelessWidget {
final String title = "Title";
Root();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: title,
),
home: Home(title: title));
}
}
class Home extends StatefulWidget {
final String title;
Home({this.title});
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
final adMobBanner = AdmobBanner(adUnitId: "id", adSize: AdmobBannerSize.BANNER);
return Scaffold(
appBar: appBar,
body: Column(
children: <Widget>[
Container(),
adMobBanner,
LocalNotification()
],
),
);
}
}
class LocalNotification extends StatefulWidget {
#override
_LocalNotificationState createState() => _LocalNotificationState();
}
class _LocalNotificationState extends State<LocalNotification> {
void initState() {
super.initState();
//initialization notification plugin
showNotification(); //the notification with onSelectNotification callback
}
Future onSelectNotification(String payload) async {
await Navigator.push(context, MaterialPageRoute(builder: (context) {
return Root(); // send you to Root again
}));
}
#override
Widget build(BuildContext context) {
return Container();
}
}
Related
how can I show a page in flutter at a specific date and time
so I have an events app when the user sign in it will navigate him
to a page that has a countdown to the date this I made
but I want to know how to navigate him to the page when the time comes
I think the solution is to display the said page while the return account will be terminated.
To do so, you would have to set a condition like this
if event.date == DateTime.now() show page contents, else show countdown
You can try this widget,
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
DateTime specificDate = DateTime.now()
.add(const Duration(seconds: 3)); //change based on your date.
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_todayIsTheDay();
});
}
_todayIsTheDay() {
Timer.periodic(const Duration(seconds: 1), (timer) {
if (DateTime.now().difference(specificDate).inSeconds ==
Duration.zero.inSeconds) {
timer.cancel();
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const SpecificPage(),
),
);
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home Page"),
),
);
}
}
class SpecificPage extends StatelessWidget {
const SpecificPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Specific Page"),
),
);
}
}
I think this can be improved
As in the titel, I have a problem with ListView and I hope you can help me out.
I am using a basic ListView to build "Card Widgets" (with their own state). The ListView uses a List of Ids, which are used to build those "Card Widgets"
The problem:
Any time I remove a card from the list by deleting an Id the ListView always removes the top most Child Widget. My backend deletes the right things, becouse after I restart the app so that the page gets populated anew, the deleted card is actually deleted and the one the removed by the ListView is visible again. It seems like ListView does not redraw it's children. Any Idea what is going on?
I created basic DartPad code to illustrate the problem
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
List<String> dd = new List<String>();
#override
void initState() {
super.initState();
dd.add('A');
dd.add('B');
dd.add('C');
dd.add('D');
}
void _incrementCounter() {
setState(() {
_counter++;
dd.insert(1, 'Q');
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title + _counter.toString()),
),
body: ListView.builder(
addAutomaticKeepAlives: false,
itemBuilder: (context, index) {
print('calling: $index :' + _counter.toString() + ' -> ' + dd[index] );
return new CRD(title: dd[index]);
},
itemCount: dd.length
),
/*
ListView(
children: dd.map((str) {
return CRD(title: str);
}).toList()
),
*/
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class CRD extends StatefulWidget {
CRD({Key key, this.title}) : super(key: key);
final String title;
#override
_CRD createState() => _CRD();
}
class _CRD extends State<CRD> {
String _val;
#override
void initState() {
super.initState();
_val = widget.title + ' ' + widget.title;
}
#override
Widget build(BuildContext context) {
return Text(_val);
}
}
So after clicking once on the Add button the list content is [A,Q,B,C,D] but the app displays [A,B,C,D,D]. Whats going on here? Am i missing something?
Your CRD widget is a StatefulWidget and the state will be reused when rebuilding since the type of the widget is the same an you did not give it a key.
To solve your issue there are a few possibilities:
Add a key to all the items in the list
Implement the didUpdateWidget method in the state of your widget
Use a statelesswidget and do the string concatination in the build method
I would like to change the body of HomeScreen from CustomDialog and it is another class. how can I do this ? I tried it in anyways but I can't do this.
this is the main file
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: HomeScreen(),));
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return SafeArea(child:Column(
children: <Widget>[
Text( Global.number.toString() ),
RaisedButton(child: Text("Click"),
onPressed: (){
showDialog(context: context,builder: (context){
return CustomDialog();
});
},)
],
));
}
}
And this is the Another File to Store global variable
class Global {
static double number = 10.0;
}
And this file is for Dialog
class CustomDialog extends StatefulWidget {
#override
_CustomDialogState createState() => _CustomDialogState();
}
class _CustomDialogState extends State<CustomDialog> {
#override
Widget build(BuildContext context) {
return Dialog(child: FlatButton(
child: Icon(Icons.add_circle,size: 30,),
onPressed: (){
setState(() {
Global.number++;
});
},
),);
}
}
You can pass the setState method of HomeScreen down to CustomDialog. I have shared a full working example based on the code you provided below.
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: HomeScreen(),));
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
void state() {
setState((){});
}
#override
Widget build(BuildContext context) {
return SafeArea(child:Column(
children: <Widget>[
Text( Global.number.toString() ),
RaisedButton(child: Text("Click"),
onPressed: (){
showDialog(context: context,builder: (context){
return CustomDialog(state);
});
},)
],
));
}
}
class Global {
static double number = 10.0;
}
class CustomDialog extends StatefulWidget {
final Function state;
CustomDialog(this.state);
#override
_CustomDialogState createState() => _CustomDialogState();
}
class _CustomDialogState extends State<CustomDialog> {
#override
Widget build(BuildContext context) {
return Dialog(child: FlatButton(
child: Icon(Icons.add_circle,size: 30,),
onPressed: (){
Global.number++;
widget.state();
},
),);
}
}
As you can see, I create a method parameter for CustomDialog and call that method following the change of Global.number. I created a wrapper to the setState function
void state() {
setState((){});
}
in HomeScreen and passed that method as the parameter to CustomDialog.
The state you set in CustomDialog is, well... the state of the dialog, not of the home screen.
To notify the home screen that some data changed, you can use a ChangeNotifierProvider to provide this data in a common super widget of home screen and the dialog, subscribe the data in the home screen, then access the data in the dialog and change it, then home screen will rebuild automatically.
Please check out Simple app state management for more detail.
I'm building android app using flutter. I have a problem to close the simple dialog programmatically.
Now I have a stateful page named ListVessel. This page contains listTile from array otherVessels.
Below is the code for this page.
class ListVessel extends StatefulWidget {
final Function() notifyParent;
ListVessel({Key key, #required this.notifyParent}) : super(key: key);
#override
_ListVesselState createState() => _ListVesselState();
}
class _ListVesselState extends State<ListVessel> {
#override
Widget build(BuildContext context) {
return ListView.separated(
separatorBuilder: (context, index) => Divider(color: Colors.blueGrey),
itemCount: otherVessels.length,
itemBuilder: (context, index) {
return ListTile(
title: Text("Name: "+otherVessels[index]["shipName"]),
onTap: () {
showDialog (
context: context,
builder: (_){
return otherTap(idx:index);
}
);
}
);
},
);
}
}
}
From above code, each tile (vessel) can be tapped and it calls otherTap() method. otherTap() method displays a simple dialog (popup) that contains the details of the tapped vessel.
Below is the code for otherTap().
class otherTap extends StatefulWidget{
otherTap({Key key, #required this.idx}) : super(key: key);
final int idx;
#override
_otherTapState createState() => new _otherTapState();
}
class _otherTapState extends State<otherTap>{
#override
Widget build(BuildContext context){
_isDialogShowing = true;
return SimpleDialog(
title: Text(otherVessels[widget.idx]["shipName"]),
children: <Widget>[
SimpleDialogOption(
child: Text('MMSI : ' + otherVessels[widget.idx]['MMSI']),
)
],
);
}
}
I have a global boolean variable (_isDialogShowing) to keep tracking if the dialog is showing.
Now i want the showdialog (popup) to dismiss after 5 second.
I use Navigator.pop() to dismiss the dialog in the MyApp function. I put it inside setstate() function.
void main() {
runApp(
MyApp(storage: CounterStorage()),
);
}
class MyApp extends StatefulWidget {
MyApp({Key key, #required this.storage}) : super(key: key);
final CounterStorage storage;
#override
State<StatefulWidget> createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
final appTitle = 'Testing applicatin';
void _update(BuildContext context) async {
await Future.delayed(Duration(milliseconds: 5000));
setState(() {
if(_isDialogShowing){
_isDialogShowing = false;
Navigator.pop(context);
//Navigator.of(context).pop();
}
});
}
#override
Widget build(BuildContext context) {
_update(context);
return new WillPopScope(
onWillPop: null,
child: new MaterialApp(
debugShowCheckedModeBanner: false,
title: appTitle,
home: MyHomePage(title: appTitle),
routes: {
Routes.home: (context) => MyHomePage(),
Routes.settings: (context) => SettingsPage(),
},
),
);
}
}
However the navigator.pop methods above doesn't close the popup.
Can anyone help?
You need to call pop on the context that you receive in builder of showDialog(), only then the dialog will pop that was created by that showDialog().
Replace your showDialog() with following and it will work for you:
showDialog(
context: context,
builder: (BuildContext context) {
Future.delayed(Duration(seconds: 5)).then((_) {
Navigator.pop(context);
});
return otherTap(idx:index);
},
);
I've solved this issue using
Navigator.of(context, rootNavigator: true).pop();
I have a parent that contain a listView and a floatingActionButton i would like to hide the floatingActionButton when the user starts scrolling i have managed to do this within the parent widget but this requires the list to be rebuilt each time.
I have moved the floatingActionButton to a separate class so i can update the state and only rebuild that widget the problem i am having is passing the data from the ScrollController in the parent class to the child this is simple when doing it through navigation but seams a but more awkward without rebuilding the parent!
A nice way to rebuild only a child widget when a value in the parent changes is to use ValueNotifier and ValueListenableBuilder. Add an instance of ValueNotifier to the parent's state class, and wrap the widget you want to rebuild in a ValueListenableBuilder.
When you want to change the value, do so using the notifier without calling setState and the child widget rebuilds using the new value.
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
ValueNotifier<bool> _notifier = ValueNotifier(false);
#override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(onPressed: () => _notifier.value = !_notifier.value, child: Text('toggle')),
ValueListenableBuilder(
valueListenable: _notifier,
builder: (BuildContext context, bool val, Widget? child) {
return Text(val.toString());
}),
],
);
}
#override
void dispose() {
_notifier.dispose();
super.dispose();
}
}
For optimal performance, you can create your own wrapper around Scaffold that gets the body as a parameter. The body widget will not be rebuilt when setState is called in HideFabOnScrollScaffoldState.
This is a common pattern that can also be found in core widgets such as AnimationBuilder.
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(home: MyHomePage()));
class MyHomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
ScrollController controller = ScrollController();
#override
Widget build(BuildContext context) {
return HideFabOnScrollScaffold(
body: ListView.builder(
controller: controller,
itemBuilder: (context, i) => ListTile(title: Text('item $i')),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
controller: controller,
);
}
}
class HideFabOnScrollScaffold extends StatefulWidget {
const HideFabOnScrollScaffold({
Key key,
this.body,
this.floatingActionButton,
this.controller,
}) : super(key: key);
final Widget body;
final Widget floatingActionButton;
final ScrollController controller;
#override
State<StatefulWidget> createState() => HideFabOnScrollScaffoldState();
}
class HideFabOnScrollScaffoldState extends State<HideFabOnScrollScaffold> {
bool _fabVisible = true;
#override
void initState() {
super.initState();
widget.controller.addListener(_updateFabVisible);
}
#override
void dispose() {
widget.controller.removeListener(_updateFabVisible);
super.dispose();
}
void _updateFabVisible() {
final newFabVisible = (widget.controller.offset == 0.0);
if (_fabVisible != newFabVisible) {
setState(() {
_fabVisible = newFabVisible;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: widget.body,
floatingActionButton: _fabVisible ? widget.floatingActionButton : null,
);
}
}
Alternatively you could also create a wrapper for FloatingActionButton, but that will probably break the transition.
I think using a stream is more simpler and also pretty easy.
You just need to post to the stream when your event arrives and then use a stream builder to respond to those changes.
Here I am showing/hiding a component based on the focus of a widget in the widget hierarchy.
I've used the rxdart package here but I don't believe you need to. also you may want to anyway because most people will be using the BloC pattern anyway.
import 'dart:async';
import 'package:rxdart/rxdart.dart';
class _PageState extends State<Page> {
final _focusNode = FocusNode();
final _focusStreamSubject = PublishSubject<bool>();
Stream<bool> get _focusStream => _focusStreamSubject.stream;
#override
void initState() {
super.initState();
_focusNode.addListener(() {
_focusStreamSubject.add(_focusNode.hasFocus);
});
}
#override
void dispose() {
_focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
_buildVeryLargeComponent(),
StreamBuilder(
stream: _focusStream,
builder: ((context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData && snapshot.data) {
return Text("keyboard has focus")
}
return Container();
}),
)
],
),
);
}
}
You can use StatefulBuilder and use its setState function to build widgets under it.
Example:
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
int count = 0;
#override
Widget build(BuildContext context) {
return Column(
children: [
// put widget here that you do not want to update using _setState of StatefulBuilder
Container(
child: Text("I am static"),
),
StatefulBuilder(builder: (_context, _setState) {
// put widges here that you want to update using _setState
return Column(
children: [
Container(
child: Text("I am updated for $count times"),
),
RaisedButton(
child: Text('Update'),
onPressed: () {
// Following only updates widgets under StatefulBuilder as we are using _setState
// that belong to StatefulBuilder
_setState(() {
count++;
});
})
],
);
}),
],
);
}
}