I'm new in Flutter and I wonder if you can help me to find a solution to my problem.
I have a scaffold with a tabview and I want to make a filter (actions button) in the main appbar to manage tabs.
Both main and child are StatefulWidgets
here's my code of the main page :
Widget body = TabBarView(
controller: tabController,
children: <Widget>[
new First(filter: filter),
new Second(filter: filter);
new Third(filter: filter);
],
);
return Scaffold(
appBar: AppBar(
actions:[IconButton(icon: Icon(MdiIcons.filter),onPressed:
() {
// Send info to child
})],
bottom: new TabBar(
controller: tabController,
tabs: <Tab>[
new Tab(text:"Tab1"),
new Tab(text:"Tab2"),
new Tab(text:"Tab3")
],
body: new Builder(builder: (BuildContext context) {
_scaffoldContext = context;
return body;
}));
));
Children page :
Widget body = Text("Filter name here");
return Scaffold(
body:
new Builder(
builder: (context) {
snack = new DisplaySnackBar(context: context);
return body;
}),
);
}
I don't know where to put the actions of appbar to make Text on child's body changes from the main page.
I found the solution by making my parent widget as an InheritedWidget so I can access to it's data from child.
Here's an example :
class MyStatefulWidget extends StatefulWidget{
#override
MyStatefulWidgetState createState() => MyStatefulWidgetState();
static MyStatefulWidgetState of(BuildContext context){
return (context.inheritFromWidgetOfExactType(_MyInheritedStateContainer) as _MyInheritedStateContainer).data;
}
}
class MyStatefulWidgetState extends State<MyStatefulWidget>{
String variableCalledHello = "Hello World";
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return new _MyInheritedStateContainer(data:this,child:/* Body here */);
}
}
class _MyInheritedStateContainer extends InheritedWidget{
_MyInheritedStateContainer({
Key key,
#required Widget child,
#required this.data,
}) : super(key: key, child: child);
final MyStatefulWidgetState data;
#override
bool updateShouldNotify(_MyInheritedStateContainer oldWidget) {
return true;
}
}
and for every child :
you just have to call MyStatefulWidgetState state = MyStatefulWidgetState.of(context);
example :
#override
Widget build(BuildContext context) {
MyStatefulWidgetState state = MyStatefulWidgetState.of(context);
print(state.variableCalledHello);
return /* Container ... */;
}
Hope that will help someone to solve his problem.
Thanks.
Related
I am using an HtmlElementView in my flutter web project for showing a web image/pdf etc.
I am not able to figure out a way to align the element within the frame. As shown by the below sample code example, if the image size is smaller than parent widget size, it is shown in top left alignment. I have a feeling this has to do with some iframe style setting but not able to figure out.
import 'dart:html';
import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
theme: ThemeData.dark(),
home: Home(),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Material App Bar'),
),
body: InteractiveViewer(
child: Container(
alignment: Alignment.center,
width: 3000,
height: 3000,
child: EmbedWebView(
src:
'https://picsum.photos/id/1/200/300', //'https://www.youtube.com/embed/3fB1mxOsqJE',
width: 500,
height: 550))),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {},
),
);
}
}
class EmbedWebView extends StatefulWidget {
final Key? key;
final String src;
final double height, width;
EmbedWebView(
{this.key, required this.src, required this.height, required this.width});
#override
State<StatefulWidget> createState() {
return EmbedWebViewState();
}
}
class EmbedWebViewState extends State<EmbedWebView>
with AutomaticKeepAliveClientMixin {
#override
void initState() {
super.initState();
final IFrameElement _iframeElement = IFrameElement()
..height = '100%'
..width = '100%'
..src = widget.src
..style.border = 'none'
..style.overflow = "hidden"
..allow = "autoplay"
..allowFullscreen = true;
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
widget.src,
(int viewId) => _iframeElement,
);
}
#override
void dispose() {
super.dispose();
}
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Center(
child: Container(
color: Colors.white,
width: widget.width,
height: widget.height,
child: HtmlElementView(
// key: UniqueKey(),
viewType: widget.src,
),
),
);
}
}
When the above code is run using flutter run -d chrome, this is what I get. It can be seen that the image in not centred.
sample code output
Try wrapping the EmvedWebView with an Expanded. Probably the EmvedWebView is not occupying the whole Container, implying that even if the Container is centered his child is not
I am trying to call a custom widget that has a function as a parameter, however I got no clue what parameter I could assign to it. I have tested all the ideas I came up with but with no success. The idea for this, I have gained from a tutorial, however I have done some things differently, as I have different requirements.
This is my code:
import 'package:flutter/material.dart';
class NewTransaction extends StatelessWidget {
final Function addTx;
const NewTransaction({Key? key, required this.addTx}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text('I cant solve this problem');
}
}
========================================================================
import 'package:flutter/material.dart';
import 'package:function_parameter_problem/new_transaction.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
NewTransaction(
addTx:
addTx), // What parameter can/should I pass here? It is crucial for my project
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Edit: Okay, but let's say that the NewTransaction() function returnes a scaffold with appbar, etc and I have to call it again, however in the following scenario:
_wykonajZapytanie() { //function called onPressed in the main window
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NewTransaction()),
); /*I have to put a parameter here, but I just want to display another window, I don't know why, but I just can't get any ideas*/
}
IMPORTANT TO NOTICE In the original project, if it comes to the first example I have provided, I do not call NewTransaction(addTx...) in the main class, but in the UserTransaction class.
tldr I need to assemble an expandable list in the main class - which can be done the way below from what I know, but also I need to call this function in the the main class on the onPressed of a button to display all of the textfields, etc.
import 'package:flutter/material.dart';
import './new_transaction.dart';
import './transaction_list.dart';
import '../models/transaction.dart';
class UserTransaction extends StatefulWidget {
#override
_UserTransactionState createState() => _UserTransactionState();
}
class _UserTransactionState extends State<UserTransaction> {
final List<Transaction> _userTransactions = [
Transaction(
id: 1,
date: DateTime.now(),
numTel: 911911911,
// scoring: 'Link wygasł',
user: 'Polizei pau pau'),
Transaction(
id: 2,
date: DateTime.now(),
numTel: 911911911,
// scoring: 'Link wygasł',
user: 'Tha police')
];
void _addNewTransaction(int txNumTel, /* String txScoring,*/ String txUser) {
final newTx = Transaction(
numTel: txNumTel,
/*scoring: txScoring,*/
user: 'PLZ WORK',
date: DateTime.now(),
id: 3,
);
setState(() {
_userTransactions.add(newTx);
});
}
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
NewTransaction(_addNewTransaction),
]);
}
}
import 'package:flutter/material.dart';
class NewTransaction extends StatelessWidget {
final VoidCallback function;
//if nothing returns
//else typedef CustomFunc = Function(int param); (replace int with your data type)
const NewTransaction({Key? key, required this.function}) : super(key: key);
#override
Widget build(BuildContext context) {
return InkWell(
child: Text('I cant solve this problem'),
onTap:function,
//if custom
//onTap: (){
// function(params);
//}
);
}
}
Call like below...
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
NewTransaction(
function: (){
//todo
},
//if custome function: (params){ todo },
)
],
),
),
If I understood your question correctly, this is the way you can pass a function with parameters:
import 'package:flutter/material.dart';
class NewTransaction extends StatelessWidget {
final Function() addTx;
const NewTransaction({Key? key, required this.addTx}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text('I cant solve this problem');
}
}
When passing this Function, make sure to pass it like this: NewTransaction(addTx: () => addTx());. If you pass it like this: NewTransaction(addTx: addTx()); the function gets called instantly.
When you sayFunction addTx it means any function can be passed.
If Function() addTx it means function with no parameter.
If Function(int) addTx it means function with one required positional parameter integer.
If void Function() addTx it means function with no parameter and return type should be void.
In flutter you can also use
VoidCallback which is basically void Function() written like this
final VoidCallback addTd.
.
or can use ValueChanged<T> which is basically void Function(T value)
More info at:
https://dart.dev/guides/language/language-tour#functions
I am using Google Maps for Flutter widget.
In my app map is displayed via one of the tabs of BottomNavigationBar.
And I have the following problem:
user is on Map's tab
user changes tab (by tapping on another one)
[PROBLEM] when user returns on Map's tab map redraws.
I would like to keep map as it is when user leaves Map's tab, so he can continue to work with it when he returns to it later on.
Tried to:
use PageStorage - without success.
make something like Singletone of Map's state - without success.
use AutomaticKeepAliveClientMixin (saw here), which looked promising, but still without success.
(I admit that I could have done something wrong)
Code of last attempt:
class MapScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => MapScreenState();
}
class MapScreenState extends State<MapScreen> with AutomaticKeepAliveClientMixin {
GoogleMapController mapController;
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
appBar: AppBar(
title: const Text("Map"),
),
body: GoogleMap(
onMapCreated: _onMapCreated,
)
);
}
void _onMapCreated(GoogleMapController controller) {
mapController = controller;
updateKeepAlive();
}
}
So, I just need a way to either keep MapScreen alive and unchanged, or to store its state somehow and restore it when user returns to MapScreen. Or something else which will solve the problem.
Use IndexedStack
For example:
Class _ExamplePageState extends State<ExamplePage> {
int _bottomNavIndex = 0;
final List<Widget> _children = [
WidgetOne(),
WidgetTwo(),
GoogleMap(),
]
#override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _bottomNavIndex,
children: _children,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _bottomNavIndex,
onTap: (index) {
if (_bottomNavIndex == index) return;
setState(() {
_bottomNavIndex = index;
});
}
items: [ ... ]
),
);
}
}
Widget always rebuild after you change from page to page, so, try this , use a variable for GoogleMap and reuse if it's different from null.
GoogleMap _map;
#override
Widget build(BuildContext context) {
if (_map == null){
_map = GoogleMap(
onMapCreated: _onMapCreated,
);
}
return Scaffold(
appBar: AppBar(
title: const Text("Map"),
),
body:_map,
);
}
I was just having this same problem and the mixin solved it. I think you've just missed enter the type of mixin at the end of its declaration.
class MapScreenState extends State<MapScreen> with AutomaticKeepAliveClientMixin<MapScreenState>
see this thread if anything.
AutomaticKeepAliveClientMixin
To
AutomaticKeepAliveClientMixin<MapScreenState>
I'm trying to create a form with tabs. In each tab I have TextFormField. Tabs have AutomaticKeepAliveClientMixin to keep their State. Tabs can be created dynamically. When new tab is created, it is inserted in the middle of the tabs list.
Problem: when new tab is inserted, its TextFormField keep state of the next tab and so on. It seems states keeps in order from 1 to n. Is there any way to keep the right state for tabs?
Thanks in advance.
import 'package:flutter/material.dart';
class TabTesting extends StatefulWidget {
#override
_TabTestingState createState() => _TabTestingState();
}
class _TabTestingState extends State<TabTesting> with TickerProviderStateMixin {
List<MyTab> _tabs = [
MyTab(TabData("1", "1")),
MyTab(TabData("3", "3")),
];
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = new TabController(vsync: this, length: _tabs.length);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("TabTesting"),
bottom: TabBar(
controller: _tabController,
labelPadding: EdgeInsets.symmetric(vertical: 16.0),
tabs: _tabs.map((tab) => Text(tab.tabData.name)).toList(),
),
),
body: Container(
padding: EdgeInsets.all(32.0),
child: TabBarView(
controller: _tabController,
children: _tabs,
),
),
persistentFooterButtons: <Widget>[
RaisedButton(
child: Text("Add tab"),
onPressed: () {
final newTab = MyTab(TabData("2", "2"));
final newTabs = _tabs;
newTabs.insert(1, newTab);
final index = _tabController.index;
setState(() {
_tabs = newTabs;
_tabController = TabController(
vsync: this,
length: newTabs.length,
initialIndex: index);
});
},
),
],
);
}
}
class MyTab extends StatefulWidget {
final tabData;
MyTab(this.tabData);
#override
_MyTabState createState() => _MyTabState();
}
class _MyTabState extends State<MyTab> with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
return Tab(
child: TextFormField(
decoration: InputDecoration(labelText: widget.tabData.name),
initialValue: widget.tabData.data,
),
);
}
}
class TabData {
String name;
String data;
TabData(this.name, this.data);
}
You forgot to add super.build(context); as the documentation of AutomaticKeepAliveClientMixin suggests.
#override
Widget build(BuildContext context) {
super.build(context);
return Tab(
child: TextFormField(
decoration: InputDecoration(labelText: widget.tabData.name),
initialValue: widget.tabData.data,
),
);
}
I'm trying to do what I think is a very simple Flutter app, but I can't figure out what's going wrong with my code.
I have a Flutter app with a Drawer widget. I'm using this widget to make a Navigation drawer, the Drawer has a ListView as a child and the ListView contains the View options (A, B and C) which the user can select.
Since the main page of the app (MyHomePage) extends from StatefulWidget the only thing that I do to load a new view is to call the setState method to assign the new value of my "control variable" (viewName), then I expect that Flutter executes the Widget build(BuildContext context) method of MyHomePage but with the new value of viewName this time.
All of the above works as expected, the problem with this is that in the body field of the Scaffold I have a TabBarView widget, since I want to show to the user a view with two tabs (Tab 1 and Tab 2) per each view (A, B and C).
The TabBarView children are:
-A StatefulTab object for Tab 1
-A simple Center widget for Tab 2
What I want to demonstrate here is that when you tap an option of the Drawer (Load the B view for example) the Tab 2 changes as a expected it's value, but the Tab 1 (that contains a stateful widget) not changes it's value when you tap any other option of the Drawer
Note: I must use the same StatefulTab widget for the 3 views in order to reuse the code, since the only value which changes for the 3 views is the viewName variable.
Here is the code:
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/StatefulTab.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(viewName: 'A'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.viewName}) : super(key: key);
final String viewName;
#override
_MyHomePageState createState() => new _MyHomePageState(viewName: viewName);
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
String viewName;
_MyHomePageState({Key key, this.viewName});
TabController controller;
#override
void initState() {
super.initState();
controller = new TabController(
length: 2,
vsync: this,
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var tabs = <Tab>[
new Tab(icon: new Icon(Icons.home), text: 'Tab 1'),
new Tab(icon: new Icon(Icons.account_box), text: 'Tab 2')
];
return new Scaffold(
appBar: new AppBar(
title: new Text(viewName),
),
body: new TabBarView(controller: controller, children: <Widget>[
new StatefulTab(viewName: viewName),
new Center(child: new Text('This is the Tab 2 for view $viewName'))
]),
bottomNavigationBar: new Material(
color: Colors.blue,
child: new TabBar(controller: controller, tabs: tabs),
),
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new Padding(
padding: new EdgeInsets.only(top: 50.0),
),
new ClipRect(
child: new Column(
children: <Widget>[
new ListTile(
title: new Text('Tap to load view: A'),
onTap: () => _loadView('A', context),
),
new ListTile(
title: new Text('Tap to load view: B'),
onTap: () => _loadView('B', context),
),
new ListTile(
title: new Text('Tap to load view: C'),
onTap: () => _loadView('C', context),
),
],
),
),
],
),
),
);
}
_loadView(String view, BuildContext context) {
Navigator.of(context).pop();
setState(() {
if (view == 'A') {
viewName = 'A';
} else if (view == 'B') {
viewName = 'B';
} else if (view == 'C') {
viewName = 'C';
}
});
}
}
StatefulTab.dart
import 'package:flutter/material.dart';
class StatefulTab extends StatefulWidget {
String viewName;
StatefulTab({Key key, this.viewName}) : super(key: key);
#override
StatefulTabState createState() => new StatefulTabState(viewName: viewName);
}
class StatefulTabState extends State<StatefulTab> {
String viewName;
StatefulTabState({Key key, this.viewName});
#override
Widget build(BuildContext context) {
return new Center(
child: new Text('This is the Tab 1 for View $viewName'),
);
}
}
How can I tell Flutter that takes the new value for the stateful wdiget of the Tab 1?
Is there a better way to implement a Navigation drawer with dynamic views?
Thanks in advance!
I think I found your problem. You keep the viewName as State in your Homepage and additionally in the StatefulTab. This can't really work, because for the StatefulTab the state doesn't change only because the state of the HomePage changes. I came to that conclusion by inserting print statements in the two build methods. The build method of the HomePage acts according to your desired behavior (as you already saw in the header of the scaffold), but the build method of the StatefulTab kept its state.
Further investigating and various print statements in various places led me to the conclusion, that the constructor of the StatefulTabState is not called after one of the drawer buttons is clicked. Here is a working example of your StatefulTab:
class StatefulTab extends StatefulWidget {
String viewName;
StatefulTab({Key key, this.viewName}) : super(key: key);
#override
StatefulTabState createState() => new StatefulTabState();
}
class StatefulTabState extends State<StatefulTab> {
#override
Widget build(BuildContext context) {
return new Center(
child: new Text('This is the Tab 1 for View ${widget.viewName}'),
);
}
}
Don't hesitate to comment, if you have any questions. It may be beneficial for you to have a look at this tutorial/documentation.
For implement TabBar and drawer flutter provide us following widget.
1. TabController
2. TabBar
3. Drawer
I found a simple demo of TabBar and drawer.