How to pass the widget as a parameter to the function - flutter

I want to pass some widgets as a parameter to function, does flutter support that? Below is my code. Should I pass a widget as a parameter to function?
This is the widget that I want to pass as a parameter:
class FirstWidget extends StatelessWidget {
#override
Widget build(BuildContext context){
return Container(
child: Text('i am the first'),
);
}
}
class SecondWidget extends StatelessWidget {
#override
Widget build(BuildContext context){
return Container(
child: Text('i am the second'),
);
}
}
createWidget is the important:
class Main extends StatelessWidget {
// maybe return a widget i wanna, maybe return a default widget.
Widget _createWidget(widget){
// do something to judge
if(dosomething){
return Container(
child: Text('nothing'),
);
}
// i wanna `widget()` at this postion. not when `_createWidget`
return widget();
}
#override
Widget build(BuildContext context){
return Column(
children: <Widget>[
_createWidget(FirstWidget),
_createWidget(SecondWidget),
],
);
}
}

You can pass an instance of the Widget to your function and then return it:
#override
Widget build(BuildContext context){
return Column(
children: <Widget>[
_createWidget(FirstWidget()),
_createWidget(SecondWidget()),
],
);
}
Widget _createWidget(Widget widget) {
// ... other stuff...
return widget;
}
Or if you want to defer constructing FirstWidget() and SecondWidget() until after you've called _createWidget() (for example, if you want _createWidget to return the constructed widget conditionally), you could use an anonymous function to create a thunk:
#override
Widget build(BuildContext context){
return Column(
children: <Widget>[
_createWidget(() => FirstWidget()),
_createWidget(() => SecondWidget()),
],
);
}
Widget _createWidget(Widget Function() widgetBuilder) {
// ... other stuff...
return widgetBuilder();
}

You can pass anything to a function.
Change your function definition as follow:
Widget _createWidget(Widget child){
// do something to judge
if(dosomething){
return Container(
child: Text('nothing'),
);
}
// Notice that you just return the variable and not call it as a function.
// return child(); <-- this one will result in an error
return child; // <-- this is the right way
}

Related

How to put a method returning a list of widgets inside another list of widgets? (inline style)

I have a method that returns a list of widgets.
List<Widget> _buildDrawerItems() {
return [...];
}
How to include the method inline like this?
#override
Widget build(BuildContext context) {
return Column(
children: [
...,
_buildDrawerItems(),
...
],
);
}
So far I've been using .addAll(). But I feel that's untidy.
#override
Widget build(BuildContext context) {
return Column(
children: []
..addAll([...])
..addAll(_buildDrawerItems())
..addAll([...])
);
}
I think the answer is literally just a small edit of your code. You can use the spread operator (...) like this, and it builds the list of widgets.
#override
Widget build(BuildContext context) {
return Column(
children: [
..._buildDrawerItems(),
],
);
}

Navigate part of screen from drawer

let's say I have an app with the following setup:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.grey[200],
child: Row(
children: [
MainMenu(),
Expanded(child: MainLoginScreen()),
],
),
));
}
}
I would like to know how can I navigate only the MainLoginScreen widget from the MainMenu with any .push() method.
(I found a way to navigate from a context inside the mainloginscreen,by wrapping it with a MaterialApp widget, but what if I want to use the MainMenu widget instead, which has another context)
There is a general agreement that a 'screen' is a topmost widget in the route. An instance of 'screen' is what you pass to Navigator.of(context).push(MaterialPageRoute(builder: (context) => HereGoesTheScreen()). So if it is under Scaffold, it is not a screen. That said, here are the options:
1. If you want to use navigation with 'back' button
Use different screens. To avoid code duplication, create MenuAndContentScreen class:
class MenuAndContentScreen extends StatelessWidget {
final Widget child;
MenuAndContentScreen({
required this.child,
});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.grey[200],
child: Row(
children: [
MainMenu(),
Expanded(child: child),
],
),
),
);
}
}
Then for each screen create a pair of a screen and a nested widget:
class MainLoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MenuAndContentScreen(
child: MainLoginWidget(),
);
}
}
class MainLoginWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Here goes the screen content.
}
}
2. If you do not need navigation with 'back' button
You may use IndexedStack widget. It can contain multiple widgets with only one visible at a time.
class MenuAndContentScreen extends StatefulWidget {
#override
_MenuAndContentScreenState createState() => _MenuAndContentScreenState(
initialContentIndex: 0,
);
}
class _MenuAndContentScreenState extends State<MenuAndContentScreen> {
int _index;
_MainMenuAndContentScreenState({
required int initialContentIndex,
}) : _contentIndex = initialContentIndex;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.grey[200],
child: Row(
children: [
MainMenu(
// A callback that will be triggered somewhere down the menu
// when an item is tapped.
setContentIndex: _setContentIndex,
),
Expanded(
child: IndexedStack(
index: _contentIndex,
children: [
MainLoginWidget(),
SomeOtherContentWidget(),
],
),
),
],
),
),
);
}
void _setContentIndex(int index) {
setState(() {
_contentIndex = index;
});
}
}
The first way is generally preferred as it is declrative which is a major idea in Flutter. When you have the entire widget tree statically declared, less things can go wrong and need to be tracked. Once you feel it, it really is a pleasure. And if you want to avoid back navigation, use replacement as ahmetakil has suggested in a comment: Navigator.of(context).pushReplacement(...)
The second way is mostly used when MainMenu needs to hold some state that needs to be preserved between views so we choose to have one screen with interchangeable content.
3. Using a nested Navigator widget
As you specifically asked about a nested Navigator widget, you may use it instead of IndexedStack:
class MenuAndContentScreen extends StatefulWidget {
#override
_MenuAndContentScreenState createState() => _MenuAndContentScreenState();
}
class _MenuAndContentScreenState extends State<MenuAndContentScreen> {
final _navigatorKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.grey[200],
child: Row(
children: [
MainMenu(
navigatorKey: _navigatorKey,
),
Expanded(
child: Navigator(
key: _navigatorKey,
onGenerateRoute: ...
),
),
],
),
),
);
}
}
// Then somewhere in MainMenu:
final anotherContext = navigatorKey.currentContext;
Navigator.of(anotherContext).push(...);
This should do the trick, however it is a bad practice because:
MainMenu knows that a particular Navigator exists and it should interact with it. It is better to either abstract this knowledge with a callback as in (2) or do not use a specific navigator as in (1). Flutter is really about passing information down the tree and not up.
At some point you would like to highlight the active item in MainMenu, but it is hard for MainMenu to know which widget is currently in the Navigator. This would add yet another non-down interaction.
For such interaction there is BLoC pattern
In Flutter, BLoC stands for Business Logic Component. In its simpliest form it is a plain object that is created in the parent widget and then passed down to MainMenu and Navigator, these widgets may then send events through it and listen on it.
class CurrentPageBloc {
// int is an example. You may use String, enum or whatever
// to identify pages.
final _outCurrentPageController = BehaviorSubject<int>();
Stream<int> _outCurrentPage => _outCurrentPageController.stream;
void setCurrentPage(int page) {
_outCurrentPageController.sink.add(page);
}
void dispose() {
_outCurrentPageController.close();
}
}
class MenuAndContentScreen extends StatefulWidget {
#override
_MenuAndContentScreenState createState() => _MenuAndContentScreenState();
}
class _MenuAndContentScreenState extends State<MenuAndContentScreen> {
final _currentPageBloc = CurrentPageBloc();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.grey[200],
child: Row(
children: [
MainMenu(
currentPageBloc: _currentPageBloc,
),
Expanded(
child: ContentWidget(
currentPageBloc: _currentPageBloc,
onGenerateRoute: ...
),
),
],
),
),
);
}
#override
void dispose() {
_currentPageBloc.dispose();
}
}
// Then in MainMenu:
currentPageBloc.setCurrentPage(1);
// Then in ContentWidget's state:
final _navigatorKey = GlobalKey();
late final StreamSubscription _subscription;
#override
void initState() {
super.initState();
_subscription = widget.currentPageBloc.outCurrentPage.listen(_setCurrentPage);
}
#override
Widget build(BuildContext context) {
return Navigator(
key: _navigatorKey,
// Everything else.
);
}
void _setCurrentPage(int currentPage) {
// Can't use this.context, because the Navigator's context is down the tree.
final anotherContext = navigatorKey?.currentContext;
if (anotherContext != null) { // null if the event is emitted before the first build.
Navigator.of(anotherContext).push(...); // Use currentPage
}
}
#override
void dispose() {
_subscription.cancel();
}
This has advantages:
MainMenu does not know who will receive the event, if anybody.
Any number of listeners may listen on such events.
However, there is still a fundamental flaw with Navigator. It can be navigated without MainMenu knowledge using 'back' button or by its internal widgets. So there is no single variable that knows which page is showing now. To highlight the active menu item, you would query the Navigator's stack which eliminates the benefits of BLoC.
For all these reasons I still suggest one of the first two solutions.

Flutter, calling a function inside the button

I am new to flutter and I make some practices. I have a StatelessWidget called doChange and makeChange and one StatefulWidget. This class which is statefulwidget I made child of the home page of the app also. But, I think that it is unnecessary to define here. My purpose in this case is that, I want to change the state of the button make open,make closed and at the same time the text open and close will also change. I think that class changeText has not problem but in makeChange class I have some trouble with creating constructor and function to call into onPress. The states do not change. How can i solve this or is that any way to do this without function ?
class changeText extends StatelessWidget{
final doChange;
changeText({#required this.doChange});
#override
Widget build(BuildContext context){
return Container(
//some codes
//some codes
child: doChange ? Text("open") : Text("close"),
);
}
}
class makeChange extends StatelessWidget{
final changeState;
makeChange({#required this.changeState}); // I want to add constructor here lets say onPressButton
whenPressed(){ // I want to create a function with the that constructor that I have add.
}
#override
Widget build(BuildContext context){
return Container(
//some codes
//
child: Column(
children: [
MaterialButton(
//some codes
//
onPressed: () {} // Here I want to call a function when press the button.
child: changeState ? Text("make open") : Text("make close"),
),
],
),
);
}
}
class Mainarea extends StatefulWidget{
#override
_MainareaState createState() => _mainAreaState();
}
class _MainareaState extends State<Mainarea> {
bool isChange= false;
#override
Widget build(BuildContext context){
return Container(
//some codes
//
child: Column(
children: <Widget>[
changeText(doChange: !this.isChange),
makeChange(changeState: !this.isChange),
],
),
);
}
}
I just added a final Function(bool) callback as a parameter, which can be called inside from the stateless widget, and returns to the calling function. From there you can call setState
class changeText extends StatelessWidget {
final bool doChange;
changeText({#required this.doChange});
#override
Widget build(BuildContext context) {
return Container(
//some codes
//some codes
child: doChange ? Text("open") : Text("close"),
);
}
}
class makeChange extends StatelessWidget {
final bool changeState;
final Function(bool) callback;
makeChange(
{#required
this.changeState,
#required
this.callback}); // You can rename this to onPressed or whatever
#override
Widget build(BuildContext context) {
return Container(
//some codes
//
child: Column(
children: [
MaterialButton(
//some codes
//
onPressed: () => callback( changeState),
child: changeState ? Text("make close") : Text("make open"), //I had to swap these around to make the text correct
),
],
),
);
}
}
class Mainarea extends StatefulWidget {
#override
_MainareaState createState() => _MainareaState();
}
class _MainareaState extends State<Mainarea> {
bool isChange = false;
#override
Widget build(BuildContext context) {
return Container(
//some codes
//
child: Column(
children: <Widget>[
changeText(doChange: !this.isChange),
makeChange(
changeState: !this.isChange,
callback: (bool val) {
setState(() => isChange = val); //this is your function that returns and resetst the value in the parent widget
},
),
],
),
);
}
}

How conditional render children widgets inside a stack in flutter?

I want to keep the drawerscreen in default and render the other screens in if else condition . How can i do that ? , Thanks in advance for the help `import 'package:flutter/material.dart';
class Body extends StatelessWidget {
final String showScreen;
const Body({
Key key,
this.showScreen="map",
}) : super(key:key);
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
DrawerScreen(),
MapScreen(),
// PostScreen()
],
);
}
}`
you mean something like this?
Widget _conditionedWidget(){
if(condition)
return MapScreen();
else
return PostScreen();
}
and then:
return Stack(
children: <Widget>[
DrawerScreen(),
_conditionedWidget(),
],
);

type 'Future<dynamic>' is not a subtype of type 'Widget'

I'm a new in Flutter.
I have a problem with a calling future method in constructor. I create method, that return a classes with widgets depends of selected item. The problem is that I need to call this method several times, the first time to build the body, the second time to update the body on tap. But I see error "type 'Future' is not a subtype of type 'Widget'" If I add the type of void instead Future, it will be executed once to create a body.
Code snippets:
class DataPageState extends State<DataPage> {
....
_tables() async {
if (selectedValue == "a") {
return DataA();
}
if (selectedValue == "b") {
return DataB();
}
if (selectedValue == "c") {
return DataC();
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(...
body: new Stack(children: <Widget>[
_tables(), //errors this //I need to call method this
... new Stack(children: <Widget>[
AnimatedContainer(...),
InkWell(onTap: () => setState(
() {
_tables(); //and this
},
),)])...}
You _tables() function should return some Widget. If you want to build Widget using some async call you can use FutureBuilder.
_tables() can not be async. you have to return Widget instead of Future<widget>.
Here is the demo of of how to add widget on click.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Home(),
);
}
}
class Home extends StatefulWidget {
Home({Key key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
Widget _add = Container();
test() {
_add = Text("datcdsvcdsvsvdjvkjdsvsa");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: Container(
child: Stack(
children: <Widget>[
RaisedButton(
color: Colors.amber,
child: Text("Press"),
onPressed: () {
setState(() {
test();
});
},
),
_add,
],
),
),
);
}
}
You probably should just edit the function _tables to make it synchronous.
like this:
Widget _tables() {
if (selectedValue == "a") {
return DataA();
}
if (selectedValue == "b") {
return DataB();
}
if (selectedValue == "c") {
return DataC();
}
}
Nowever, If you have some reason to make _tables asyncronous, then do this:
Tables is a type Future. You need a `futureBuilder` for this.
Stack(children: <Widget>[
FutureBuilder<Widget>(
future: _tables(),
builder: (BuildContext _, snapshot) {
if(snapshot.hasError) { // Error
return const MyErrorWidget(); // You will have to create this widget
} else if(!(snapshot.hasData)) { // Loading
return CircularProgressIndicator();
}/ Loaded without any errors
return snapshot.data; // The widget that was returned;
},
),
// the rest of the widgets in the Stack
]);
Now this won't solve the problem. You will have to add a return type to _tables().
so do this
Future<Widget> _tables() async {