Flutter add a card widget on another screen on button tap - flutter

I am working on an app in which I want to add some card widgets on the home screen when a user selects the cards on the explore screen. Since I am new to flutter I need some assistance with code.
It is like it would be adding a favourite or bookmark feature.
If you want to check the full code here is the link to the github : https://github.com/Ankitkj1999/study_lpu/tree/mark_feature
explore.dart
import 'package:flutter/material.dart';
class ExploreScreen extends StatelessWidget {
const ExploreScreen({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return ListViewHome();
}
}
class ListViewHome extends StatelessWidget {
final titles = ["List 1", "List 2", "List 3", "List 4"];
final subtitles = [
"Here is list 1 subtitle",
"Here is list 2 subtitle",
"Here is list 3 subtitle",
"Here is list 4 subtitle"
];
// final icons = [
// // IconButton(onPressed: () {}, icon: const Icon(Icons.add)),
// // Icons.access_alarm,
// // Icons.access_time,
// Icons.add
// ];
ListViewHome({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: titles.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(titles[index]),
subtitle: Text(subtitles[index]),
leading: const CircleAvatar(
backgroundImage: NetworkImage(
"https://images.unsplash.com/photo-1547721064-da6cfb341d50")),
trailing: IconButton(
onPressed: () {},
icon: const Icon(Icons.add),
),
),
);
});
}
}
class pushAdd extends StatelessWidget {
const pushAdd({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
showWidget() {
return const Scaffold(
body: Center(
child: Text('Home'),
),
);
}
return showWidget();
}
}
home.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:study_lpu/explore.dart';
import 'package:study_lpu/fav.dart';
import 'login.dart';
import 'explore.dart';
// bottom navigation tutorial https://blog.logrocket.com/how-to-build-a-bottom-navigation-bar-in-flutter/
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
late String uid;
int _selectedIndex = 0;
static const List<Widget> _pages = <Widget>[
// Scaffold(
// body: Center(
// child: Text('Hot'),
// ),
// ),
pushAdd(),
ExploreScreen(),
RandWords(),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
actions: [
IconButton(
icon: const Icon(Icons.logout),
onPressed: () async {
await FirebaseAuth.instance.signOut();
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => const LoginScreen()),
(route) => false);
},
)
],
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home_outlined),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.book_outlined),
label: 'Explore',
),
BottomNavigationBarItem(
icon: Icon(Icons.person_outlined),
label: 'Profile',
)
],
currentIndex: _selectedIndex,
onTap: _onItemTapped,
),
body:
// Center(
// // child: Text(uid),
// child: _pages.elementAt(_selectedIndex),
// ),
IndexedStack(
index: _selectedIndex,
children: _pages,
));
}
#override
void initState() {
// TODO: implement initState
super.initState();
uid = FirebaseAuth.instance.currentUser!.uid;
}
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
}

Here is how you can do that in a basic way without using any provider. Firstly you need to put your data in a model to access the variable properly. second, in home page I defined a list of model that is empty and also defined a function that adds card to the list executed in explore page. Also you need to make a view in home page to show added card to the list
class MyCardModel {
String? title;
String? subTitle;
String? leadingImage;
MyCardModel(String title, String subTitle, String leadingImage) {
this.title = title;
this.subTitle = subTitle;
this.leadingImage = leadingImage;
}
}
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
late String uid;
int _selectedIndex = 0;
List<MyCardModel> listOfCard = [];
void addCardToListMethod(MyCardModel card) {
setState(() {
listOfCard.add(card);
});
}
#override
Widget build(BuildContext context) {
List<Widget> _pages = <Widget>[
Scaffold(
body: Center(
child: ListView.builder(
itemCount: listOfCard.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(listOfCard[index].title!),
subtitle: Text(listOfCard[index].subTitle!),
leading: CircleAvatar(
backgroundImage:
NetworkImage(listOfCard[index].leadingImage!)),
),
);
})),
),
pushAdd(),
ExploreScreen(addCardToListMethod: addCardToListMethod),
RandWords(),
];
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
actions: [
IconButton(
icon: const Icon(Icons.logout),
onPressed: () async {
await FirebaseAuth.instance.signOut();
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => const LoginScreen()),
(route) => false);
},
)
],
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home_outlined),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.book_outlined),
label: 'Explore',
),
BottomNavigationBarItem(
icon: Icon(Icons.person_outlined),
label: 'Profile',
)
],
currentIndex: _selectedIndex,
onTap: _onItemTapped,
),
body:
// Center(
// // child: Text(uid),
// child: _pages.elementAt(_selectedIndex),
// ),
IndexedStack(
index: _selectedIndex,
children: _pages,
));
}
#override
void initState() {
// TODO: implement initState
super.initState();
uid = FirebaseAuth.instance.currentUser!.uid;
}
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
}
class ExploreScreen extends StatelessWidget {
Function? addCardToListMethod;
ExploreScreen({Key? key, this.addCardToListMethod}) : super(key: key);
List<MyCardModel> myCardList = [
MyCardModel("List1", "here is list 1 subtitle",
"https://images.unsplash.com/photo-1547721064-da6cfb341d50"),
MyCardModel("List1", "here is list 1 subtitle",
"https://images.unsplash.com/photo-1547721064-da6cfb341d50")
];
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: myCardList.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(myCardList[index].title!),
subtitle: Text(myCardList[index].subTitle!),
leading: CircleAvatar(
backgroundImage:
NetworkImage(myCardList[index].leadingImage!)),
trailing: IconButton(
onPressed: () {
addCardToListMethod!(myCardList[index]);
},
icon: const Icon(Icons.add),
),
),
);
});
}
}

Related

Is it possible to use a bottomNavBar for mobile and a Sidebar for desktop in flutter?

I am currently developing a cross platform app in which I wanted to use a Sidebar for navigation on desktop and tablet view, but a bottom navigation bar for mobile, as it isn't really handy to use a sidebar on mobile.
I have trouble with the navigation part, as for the sidebar I can just easily use the push() function. But with the bottomNavBar I have to use the onItemTapped function with indexes etc. Is there an easy way to use them together/switch between them?
This is my navigation for the Sidebar:
#override
Widget build(BuildContext context) {
return ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => page),
);
},
And this is how I tried to do the bottomNavBar navigation:
currentIndex: _selectedIndex,
onTap: _onItemTapped,
),
body: PageNavigationItem.items.elementAt(_selectedIndex),
);
} // build method
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
Yes it is possible and once check below example code.
Video of how it will works.
https://drive.google.com/file/d/1BxK6qevJOu4qYrmnoTXdIYtqLAVC87ya/view?usp=share_link
Here we are creating a model for Title and onTap
class DataModel {
final String labelName;
final Function onTap;
const DataModel({required this.labelName, required this.onTap});
}
Here we are creating a list of DataModel so will use in Title and onTap.
List<DataModel> dataList = [
DataModel(
labelName: "First",
onTap: () {
print("first");
}),
DataModel(
labelName: "Second",
onTap: () {
print("Second");
}),
DataModel(
labelName: "Third",
onTap: () {
print("Third");
}),
DataModel(
labelName: "Fourth",
onTap: () {
print("Fourth");
}),
];
Function for get device is mobile or tablet
getDevice() {
return MediaQuery.of(context).size.width <= 800 ? "Mobile" : "Tablet";
}
here is full code of that page.
class MyHomePage extends StatefulWidget {
const MyHomePage({
super.key,
});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo Home Page"),
),
drawer: getDevice() == "Tablet"
? Drawer(
child: ListView.builder(
itemCount: dataList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(dataList[index].labelName),
onTap: () {
dataList[index].onTap();
},
);
},
))
: null,
bottomNavigationBar: getDevice() == "Mobile"
? BottomNavigationBar(
onTap: (value) {
dataList[value].onTap();
},
// backgroundColor: Colors.black,
items: dataList.map((e) => BottomNavigationBarItem(backgroundColor: Colors.black, icon: Icon(Icons.add), label: e.labelName)).toList(),
// items: <BottomNavigationBarItem>[
// BottomNavigationBarItem(label: "Test", icon: Icon(Icons.add)),
// BottomNavigationBarItem(label: "Test1", icon: Icon(Icons.add)),
// BottomNavigationBarItem(label: "Test2", icon: Icon(Icons.add)),
// BottomNavigationBarItem(label: "Test3", icon: Icon(Icons.add)),
// ],
)
: null,
body: Center(
child: TextButton(
onPressed: () {
setState(() {
// isHide = !isHide;
});
},
child: Text("Hide")),
),
);
}
I Hope these things are solve your issue.
There is, to my knowledge, no way to solve your issue unless you "make your own bottom navigation bar".
I would however ask if you don't want to use a Drawer widget instead of a bottom navigation bar as it is a way to keep your app consistent across platforms, follows flutters guidelines for projects and permit you to use push. It is a "sidebar" in a sense.
I would do my own bottom navigation bar if I felt I needed it no matter what,something like this:
import 'package:flutter/material.dart';
class BottomNavigationBarWidget extends StatelessWidget {
final List<Widget> children;
final Widget body;
const BottomNavigationBarWidget({Key? key, required this.children, required this.body}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(child: body),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
)
],
);
}
}
with this in the main page
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter app'),
),
body: BottomNavigationBarWidget(
body: Center(
child: Text('Hello world!'),
),
children: [
Column(children:[Icon(Icons.percent), Text("Test")])
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
print('Zapp!');
},
backgroundColor: Colors.yellow[700],
child: Icon(
Icons.bolt,
color: Colors.black,
),
),
);
}
}
This is the result
But seriously, it is just easier and better to use a Drawer widget

How to change the BottomNavigationBar after a Navigator.push?

I would like to set a new BottomNavigationBar after i've clicked on one of my ListTile. Right now, i am getting two BottomNavigationbar after I've clicked on one of them.
Below is my code where I setup the first bar:
class CoachNav extends StatefulWidget {
const CoachNav({Key? key}) : super(key: key);
#override
State<CoachNav> createState() => _CoachNavState();
}
class _CoachNavState extends State<CoachNav> {
int _selectedIndex = 0;
final List<Widget> _widgetOptions = <Widget>[
const TeamListView(),
const SettingsFormView(),
];
Widget? _onItemTap(int index) {
setState(() {
_selectedIndex = index;
});
return null;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.groups),
label: "Mes équipes",
),
BottomNavigationBarItem(
icon: Icon(Icons.settings), label: "Paramètres"),
],
currentIndex: _selectedIndex,
onTap: _onItemTap,
),
);
}
}
Then, there is the code where I setup the second bar:
class TeamNav extends StatefulWidget {
const TeamNav({Key? key}) : super(key: key);
#override
State<TeamNav> createState() => _TeamNavState();
}
class _TeamNavState extends State<TeamNav> {
int _selectedIndex = 0;
final List<Widget> _widgetOptions = <Widget>[
const PlayersListView(),
const GamesListView(),
];
Widget? _onItemTap(int index) {
setState(() {
_selectedIndex = index;
});
return null;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.group),
label: "Mes joueurs",
),
BottomNavigationBarItem(
icon: Icon(Icons.sports_basketball), label: "Mes matchs"),
],
currentIndex: _selectedIndex,
onTap: _onItemTap,
),
);
}
}
Here are two screenshots of what is happening
First Bar
Second Bar
---------------- EDIT ----------------------
This is what I get when I make the _widgetOptions texts
I got the first bar... whith the content from where the second bar should Appear
this is the snippet of the code I got as answer below:
lass App extends StatelessWidget {
const App({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(home: CoachNav());
}
}
class TeamNav extends StatefulWidget {
const TeamNav({Key? key}) : super(key: key);
#override
State<TeamNav> createState() => _TeamNavState();
}
class _TeamNavState extends State<TeamNav> {
int _selectedIndex = 0;
final List<Widget> _widgetOptions = <Widget>[
Text("PlayersListView"),
Text("PlayersListView"),
];
Widget? _onItemTap(int index) {
setState(() {
_selectedIndex = index;
});
return null;
}
#override
Widget build(BuildContext context) {
return Scaffold(
/* floatingActionButton: FloatingActionButton(onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => CoachNav(),
));
}),*/
body: Container(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.group),
label: "Mes joueurs",
),
BottomNavigationBarItem(
icon: Icon(Icons.sports_basketball), label: "Mes matchs"),
],
currentIndex: _selectedIndex,
onTap: _onItemTap,
),
);
}
}
class CoachNav extends StatefulWidget {
const CoachNav({Key? key}) : super(key: key);
#override
State<CoachNav> createState() => _CoachNavState();
}
class _CoachNavState extends State<CoachNav> {
int _selectedIndex = 0;
final List<Widget> _widgetOptions = <Widget>[
Text("PlayersListView"),
Text("PlayersListView"),
];
Widget? _onItemTap(int index) {
setState(() {
_selectedIndex = index;
});
return null;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: _widgetOptions.elementAt(_selectedIndex),
),
//floatingActionButton: FloatingActionButton(onPressed: () {}),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.groups),
label: "Mes équipes",
),
BottomNavigationBarItem(
icon: Icon(Icons.settings), label: "Paramètres"),
],
currentIndex: _selectedIndex,
onTap: _onItemTap,
),
);
}
}
This is the code that have the text shown in the third screenshot.
class PlayersListView extends StatelessWidget {
const PlayersListView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(body: Text("Playerslist"),);
}
}
I think you have a design problem.
In your case, the best way is this one, i think.
When you tap on a tile you should fix a flag and rebuild your page instead of navigating to a new route.
Then, when building your BottomNavigationBarItemlist check the flag and add or remove BottomNavigationBarItem as you need.
Remove extra scaffold from _widgetOptions children that contains bottomNavBar. Follow the snippet pattern
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(home: TeamNav());
}
}
class TeamNav extends StatefulWidget {
const TeamNav({Key? key}) : super(key: key);
#override
State<TeamNav> createState() => _TeamNavState();
}
class _TeamNavState extends State<TeamNav> {
int _selectedIndex = 0;
final List<Widget> _widgetOptions = <Widget>[
Text("PlayersListView"),
Text(" GamesListView()"),
];
Widget? _onItemTap(int index) {
setState(() {
_selectedIndex = index;
});
return null;
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => CoachNav(),
));
}),
body: Container(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.group),
label: "Mes joueurs",
),
BottomNavigationBarItem(
icon: Icon(Icons.sports_basketball), label: "Mes matchs"),
],
currentIndex: _selectedIndex,
onTap: _onItemTap,
),
);
}
}
class CoachNav extends StatefulWidget {
const CoachNav({Key? key}) : super(key: key);
#override
State<CoachNav> createState() => _CoachNavState();
}
class _CoachNavState extends State<CoachNav> {
int _selectedIndex = 0;
final List<Widget> _widgetOptions = <Widget>[
Text("TeamListView"),
Text("SettingsFormView"),
];
Widget? _onItemTap(int index) {
setState(() {
_selectedIndex = index;
});
return null;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: _widgetOptions.elementAt(_selectedIndex),
),
floatingActionButton: FloatingActionButton(onPressed: () {}),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.groups),
label: "Mes équipes",
),
BottomNavigationBarItem(
icon: Icon(Icons.settings), label: "Paramètres"),
],
currentIndex: _selectedIndex,
onTap: _onItemTap,
),
);
}
}
Ok so I found a solution, I simply needed to add "rootNavigator: true" to my Navigator.push. It works as intended now.

Flutter - Call a function defined in another dart file to refresh a ListView

I have 2 dart files home.dart and video_list.dart.
I have a PopupMenuButton and a BottomNavigationBar in home.dart. video_list.dart is shown in the "body:" of home.dart.
Each time the value changes in PopupMenuButton in home.dart, I want to call a function in video_list.dart (_loadPlaylist()) so that the ListView in video_list.dart is refreshed.
How is this possible? I am a newbie.
home.dart
import 'package:crt/models/subject.dart';
import 'package:crt/pages/faq.dart';
import 'package:crt/pages/quiz.dart';
import 'package:crt/pages/video_list.dart';
import 'package:crt/pages/resource_list.dart';
import 'package:flutter/material.dart';
import 'package:crt/data/subjects.dart';
class Home extends StatefulWidget {
Home({Key? key}) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
//GlobalKey<_VideoListState> _key = GlobalKey<_VideoListState>();
Subject _selectedSubject = subjects[0];
int _currentIndex = 0;
List<StatefulWidget> pages = [];
#override
void initState() {
super.initState();
_setPages();
}
void _setPages() {
pages = [
VideoList(_selectedSubject),
ResourceList(_selectedSubject),
Quiz(_selectedSubject),
Faq(_selectedSubject),
];
}
IndexedStack _loadPage() {
return IndexedStack(
index: _currentIndex,
children: pages,
);
}
void _changeSubject(Subject subject) {
setState(() {
_selectedSubject = subject;
_setPages();
if (_selectedSubject.name == 'English') {
// I want to call _loadPlaylist function defined in video_list.dart
// VideoList()._loadPlaylist;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_selectedSubject.name),
elevation: 0,
actions: [
PopupMenuButton<Subject>(
onSelected: _changeSubject,
itemBuilder: (BuildContext context) {
return subjects.map((Subject subject) {
return PopupMenuItem<Subject>(
value: subject,
child: Text(subject.name),
);
}).toList();
},
)
],
),
body: _loadPage(),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
iconSize: 30,
selectedFontSize: 14,
unselectedFontSize: 14,
currentIndex: _currentIndex,
onTap: (index) => setState(() {
_currentIndex = index;
}),
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.video_library),
label: 'Videos',
),
BottomNavigationBarItem(
icon: Icon(Icons.library_books),
label: 'Resources',
),
BottomNavigationBarItem(
icon: Icon(Icons.quiz),
label: 'Quiz',
),
BottomNavigationBarItem(
icon: Icon(Icons.question_answer),
label: 'FAQ',
),
],
),
);
}
}
video_list.dart
import 'package:crt/models/subject.dart';
import 'package:flutter/material.dart';
import 'package:crt/models/playlist.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:crt/utilities/services.dart';
class VideoList extends StatefulWidget {
VideoList(Subject this.selectedSubject, {Key? key}) : super(key: key);
final Subject selectedSubject;
#override
State<VideoList> createState() => _VideoListState();
}
class _VideoListState extends State<VideoList> {
late Playlist _playlist;
bool _isLoading = true;
late String _name;
#override
void initState() {
super.initState();
_playlist = Playlist();
_playlist.items = List.empty(growable: true);
_loadPlaylist();
}
//I want to call this function from home.dart
_loadPlaylist() async {
String playlistId = widget.selectedSubject.playlistId;
Playlist playlist = await Services.getPlaylist(playlistId: playlistId);
_playlist.items?.addAll(playlist.items as Iterable<Item>);
setState(() {
_isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: ListView.builder(
itemCount: _playlist.items?.length,
itemBuilder: ((context, index) {
Item item = _playlist.items![index];
return Card(
child: InkWell(
onTap: () async {
// Navigator.push(context, MaterialPageRoute(builder: (context) {
// return VideoPlayer(
// item: item,
// );
// }));
},
child: Row(children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: CachedNetworkImage(
imageUrl:
item.snippet.thumbnails.thumbnailsDefault.url),
),
SizedBox(
width: 10.0,
),
Flexible(
child: Text(
item.snippet.title,
style: TextStyle(
fontSize: 18.0,
),
)),
const SizedBox(
width: 20.0,
),
]),
),
);
})),
);
}
}
Just move the _loadPlayList() function to Home and pass it down to VideoList widget.
class VideoList extends StatefulWidget {
VideoList(Subject this.selectedSubject, {Key? key, this.loadPlayList})
: super(key: key);
final Subject selectedSubject;
final Future<Function> loadPlayList;
#override
State<VideoList> createState() => _VideoListState();
}
The whole code - home.dart
import 'package:crt/models/subject.dart';
import 'package:crt/pages/faq.dart';
import 'package:crt/pages/quiz.dart';
import 'package:crt/pages/video_list.dart';
import 'package:crt/pages/resource_list.dart';
import 'package:flutter/material.dart';
import 'package:crt/data/subjects.dart';
class Home extends StatefulWidget {
Home({Key? key}) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
//GlobalKey<_VideoListState> _key = GlobalKey<_VideoListState>();
Subject _selectedSubject = subjects[0];
int _currentIndex = 0;
List<StatefulWidget> pages = [];
#override
void initState() {
super.initState();
_setPages();
}
void _setPages() {
pages = [
VideoList(_selectedSubject, loadPlayList()),
ResourceList(_selectedSubject),
Quiz(_selectedSubject),
Faq(_selectedSubject),
];
}
loadPlaylist() async {
String playlistId = widget.selectedSubject.playlistId;
Playlist playlist = await Services.getPlaylist(playlistId: playlistId);
_playlist.items?.addAll(playlist.items as Iterable<Item>);
}
IndexedStack _loadPage() {
return IndexedStack(
index: _currentIndex,
children: pages,
);
}
void _changeSubject(Subject subject) {
setState(() {
_selectedSubject = subject;
_setPages();
if (_selectedSubject.name == 'English') {
this.loadPlaylist();
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_selectedSubject.name),
elevation: 0,
actions: [
PopupMenuButton<Subject>(
onSelected: _changeSubject,
itemBuilder: (BuildContext context) {
return subjects.map((Subject subject) {
return PopupMenuItem<Subject>(
value: subject,
child: Text(subject.name),
);
}).toList();
},
)
],
),
body: _loadPage(),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
iconSize: 30,
selectedFontSize: 14,
unselectedFontSize: 14,
currentIndex: _currentIndex,
onTap: (index) => setState(() {
_currentIndex = index;
}),
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.video_library),
label: 'Videos',
),
BottomNavigationBarItem(
icon: Icon(Icons.library_books),
label: 'Resources',
),
BottomNavigationBarItem(
icon: Icon(Icons.quiz),
label: 'Quiz',
),
BottomNavigationBarItem(
icon: Icon(Icons.question_answer),
label: 'FAQ',
),
],
),
);
}
}
video_list.dart
import 'package:crt/models/subject.dart';
import 'package:flutter/material.dart';
import 'package:crt/models/playlist.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:crt/utilities/services.dart';
class VideoList extends StatefulWidget {
VideoList(Subject this.selectedSubject, this.loadPlayList, {Key? key})
: super(key: key);
final Subject selectedSubject;
final Future<Function> loadPlayList;
#override
State<VideoList> createState() => _VideoListState();
}
class _VideoListState extends State<VideoList> {
late Playlist _playlist;
bool _isLoading = true;
late String _name;
#override
void initState() {
super.initState();
_playlist = Playlist();
_playlist.items = List.empty(growable: true);
widget.loadPlaylist().then(() {
setState(() {
_isLoading = false;
});
};
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: ListView.builder(
itemCount: _playlist.items?.length,
itemBuilder: ((context, index) {
Item item = _playlist.items![index];
return Card(
child: InkWell(
onTap: () async {
// Navigator.push(context, MaterialPageRoute(builder: (context) {
// return VideoPlayer(
// item: item,
// );
// }));
},
child: Row(children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: CachedNetworkImage(
imageUrl:
item.snippet.thumbnails.thumbnailsDefault.url),
),
SizedBox(
width: 10.0,
),
Flexible(
child: Text(
item.snippet.title,
style: TextStyle(
fontSize: 18.0,
),
)),
const SizedBox(
width: 20.0,
),
]),
),
);
})),
);
}
}

Updating a Stateless Widget from a Stateful Widget

Main.dart:
import 'package:flutter/material.dart';
import 'package:mypackage/widgets/home_widget.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "My App",
theme: ThemeData(primarySwatch: Colors.blueGrey),
home: Home(),
);
}
}
Home.dart:
import 'package:flutter/material.dart';
import 'package:mypackage/widgets/secondary_page.dart';
import 'package:mypackage/widgets/home_view.dart';
import 'package:mypackage/widgets/settings_page.dart';
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomeState();
}
}
class _HomeState extends State<Home> {
int currentPageIndex = 0;
final List<Widget> pageWidgets = [HomeView(), SecondaryPage(), SettingsPage()];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("My App"),
centerTitle: true,
),
body: pageWidgets[currentPageIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped,
currentIndex: currentPageIndex,
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.home), label: "Tab One"),
BottomNavigationBarItem(
icon: new Icon(Icons.account_balance), label: "Tab Two"),
BottomNavigationBarItem(
icon: new Icon(Icons.settings), label: "Tab Three"),
],
),
floatingActionButton: FloatingActionButton(onPressed: () {
// Update data from here
HomeView().updateDisplay();
}));
}
// Changes the index when the tapped page has loaded
void onTabTapped(int index) {
setState(() {
currentPageIndex = index;
});
}
}
HomeView.dart:
import 'package:flutter/material.dart';
import 'package:mypackage/models/mydisplay.dart';
import 'package:mypackage/network/mydata.dart';
class HomeView extends StatelessWidget {
final List<MyDisplay> dataList = [
new MyDisplay("Sample Display",
"SampleFile", 0),
];
Future<void> updateDisplay() async {
MyData myData = new MyData();
dataList.forEach((d) async {
d.changePercentage = await myData
.getDataPercentage("assets/data/" + d.fileName + ".xml");
print(d.dataName +
" change percentage: " +
d.changePercentage.toString() +
"%");
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: new ListView.builder(
itemCount: dataList.length,
itemBuilder: (BuildContext context, int index) =>
buildCard(context, index)),
);
}
Widget buildCard(BuildContext context, int index) {
return Container(
child: Card(
color: Colors.teal.shade200,
child: Padding(
padding: const EdgeInsets.all(25),
child: Column(children: <Widget>[
Text(dataList[index].dataName,
style: new TextStyle(
fontSize: 20, fontWeight: FontWeight.bold)),
Text(dataList[index].changePercentage.toString() + "%")
]))));
}
}
When I call the updateDisplay() method from the floatingActionButton it works as expected, the data gets printed to the console, however it doesn't seem to update the actual UI, the content of the cards, they simply remain at their initial set value.
To clarify: how do I update the content of the cards inside the listview (inside a stateless widget) from a stateful widget?
I am sorry if this is a duplicate question, I have looked and am continuing looking for answers to the question but I simply can't get it working.
You need to use a stateful widget since you cannot update stateless widgets. Check this link out for more information.The next problem, calling it, can be fixed by using globalKeys. The only weird thing about this is you have to keep both the Home stateful widget and HomeView stateful widget in the same dart file. . Try this code to make it work.
HOME VIEW
import 'package:flutter/material.dart';
import 'package:mypackage/widgets/secondary_page.dart';
import 'package:mypackage/widgets/settings_page.dart';
import 'package:mypackage/models/mydisplay.dart';
import 'package:mypackage/network/mydata.dart';
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomeState();
}
}
class _HomeState extends State<Home> {
int currentPageIndex = 0;
final List<Widget> pageWidgets = [HomeView(), SecondaryPage(), SettingsPage()];
GlobalKey<_HomeViewState> _myKey = GlobalKey();// We declare a key here
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("My App"),
centerTitle: true,
),
body: pageWidgets[currentPageIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped,
currentIndex: currentPageIndex,
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.home), label: "Tab One"),
BottomNavigationBarItem(
icon: new Icon(Icons.account_balance), label: "Tab Two"),
BottomNavigationBarItem(
icon: new Icon(Icons.settings), label: "Tab Three"),
],
),
floatingActionButton: FloatingActionButton(onPressed: () {
// Update data from here
_myKey.currentState.updateDisplay();//This is how we call the function
}));
}
// Changes the index when the tapped page has loaded
void onTabTapped(int index) {
setState(() {
currentPageIndex = index;
});
}
}
class HomeView extends StatefulWidget {
Function updateDisplay;
HomeView({Key key}): super(key: key);//This key is what we use
#override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
final List<MyDisplay> dataList = [
new MyDisplay("Sample Display",
"SampleFile", 0),
];
#override
void initState(){
super.initState();
widget.updateDisplay = () async {
MyData myData = new MyData();
dataList.forEach((d) async {
d.changePercentage = await myData
.getDataPercentage("assets/data/" + d.fileName + ".xml");
print(d.dataName +
" change percentage: " +
d.changePercentage.toString() +
"%");
});
setState((){});//This line rebuilds the scaffold
};
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: new ListView.builder(
itemCount: dataList.length,
itemBuilder: (BuildContext context, int index) =>
buildCard(context, index)),
);
}
Widget buildCard(BuildContext context, int index) {
return Container(
child: Card(
color: Colors.teal.shade200,
child: Padding(
padding: const EdgeInsets.all(25),
child: Column(children: <Widget>[
Text(dataList[index].dataName,
style: new TextStyle(
fontSize: 20, fontWeight: FontWeight.bold)),
Text(dataList[index].changePercentage.toString() + "%")
]))));
}
}
We use setState((){}) to rebuild the widget. Just keep in mind one think. Wherever you use the updateDisplay method, dont make it so it runs in a loop after rebuilding. For eg. You use it in a future builder inside the child. THat way it will keep rebuilding
EDIT
We added a key to HomeView and now we can use that key like _myKey.currentState.yourFunction. Hope I helped
While I understand what you wish to achieve, I believe that will be going against "the Flutter way". You can achieve your desired result using a key, but I believe it will be easier and more idiomatic to lift the state up the widget tree. You can read about it in the docs here.
So in your case, you might change it to:
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomeState();
}
}
class _HomeState extends State<Home> {
int currentPageIndex = 0;
// Added this line
List<MyDisplay> dataList = [
new MyDisplay("Sample Display",
"SampleFile", 0),
];
final List<Widget> pageWidgets;
// moved the update function from HomeView to here
Future<void> updateDisplay() async {
MyData myData = new MyData();
dataList.forEach((d) async {
d.changePercentage = await myData
.getDataPercentage("assets/data/" + d.fileName + ".xml");
print(d.dataName +
" change percentage: " +
d.changePercentage.toString() +
"%");
});
setState((){});
}
#override
Widget build(BuildContext context) {
pageWidgets = [HomeView(dataList: dataList), SecondaryPage(), SettingsPage()]
return Scaffold(
appBar: AppBar(
title: Text("My App"),
centerTitle: true,
),
body: pageWidgets[currentPageIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped,
currentIndex: currentPageIndex,
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.home), label: "Tab One"),
BottomNavigationBarItem(
icon: new Icon(Icons.account_balance), label: "Tab Two"),
BottomNavigationBarItem(
icon: new Icon(Icons.settings), label: "Tab Three"),
],
),
floatingActionButton: FloatingActionButton(onPressed: () {
updateDisplay();
}));
}
// Changes the index when the tapped page has loaded
void onTabTapped(int index) {
setState(() {
currentPageIndex = index;
});
}
}
Then for your HomeView widget:
class HomeView extends StatelessWidget {
HomeView({this.dataList});
final List<MyDisplay> dataList;
#override
Widget build(BuildContext context) {
return Scaffold(
body: new ListView.builder(
itemCount: dataList.length,
itemBuilder: (BuildContext context, int index) =>
buildCard(context, index)),
);
}
Widget buildCard(BuildContext context, int index) {
return Container(
child: Card(
color: Colors.teal.shade200,
child: Padding(
padding: const EdgeInsets.all(25),
child: Column(children: <Widget>[
Text(dataList[index].dataName,
style: new TextStyle(
fontSize: 20, fontWeight: FontWeight.bold)),
Text(dataList[index].changePercentage.toString() + "%")
],
),
),
),
);
}
}
I haven't tested if this works. I probably will a little later on.
You need to call setState(() {}); after HomeView().updateDisplay();
update your home.dart as follows.
import 'package:flutter/material.dart';
import 'package:mypackage/widgets/secondary_page.dart';
import 'package:mypackage/widgets/home_view.dart';
import 'package:mypackage/widgets/settings_page.dart';
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomeState();
}
}
class _HomeState extends State<Home> {
int currentPageIndex = 0;
Widget _homeView = HomeView();
final List<Widget> pageWidgets = [_homeView, SecondaryPage(), SettingsPage()];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("My App"),
centerTitle: true,
),
body: pageWidgets[currentPageIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped,
currentIndex: currentPageIndex,
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.home), label: "Tab One"),
BottomNavigationBarItem(
icon: new Icon(Icons.account_balance), label: "Tab Two"),
BottomNavigationBarItem(
icon: new Icon(Icons.settings), label: "Tab Three"),
],
),
floatingActionButton: FloatingActionButton(onPressed: () {
// Update data from here
_homeView.updateDisplay();
}));
}
// Changes the index when the tapped page has loaded
void onTabTapped(int index) {
setState(() {
currentPageIndex = index;
});
}
}
home_view.dart as follows
import 'package:flutter/material.dart';
import 'package:mypackage/models/mydisplay.dart';
import 'package:mypackage/network/mydata.dart';
class HomeView extends StatelessWidget {
final List<MyDisplay> dataList = [
new MyDisplay("Sample Display",
"SampleFile", 0),
];
Future<void> updateDisplay() async {
MyData myData = new MyData();
dataList.forEach((d) async {
d.changePercentage = await myData
.getDataPercentage("assets/data/" + d.fileName + ".xml");
print(d.dataName +
" change percentage: " +
d.changePercentage.toString() +
"%");
});
setState((){});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: new ListView.builder(
itemCount: dataList.length,
itemBuilder: (BuildContext context, int index) =>
buildCard(context, index)),
);
}
Widget buildCard(BuildContext context, int index) {
return Container(
child: Card(
color: Colors.teal.shade200,
child: Padding(
padding: const EdgeInsets.all(25),
child: Column(children: <Widget>[
Text(dataList[index].dataName,
style: new TextStyle(
fontSize: 20, fontWeight: FontWeight.bold)),
Text(dataList[index].changePercentage.toString() + "%")
]))));
}
}
You need to transform your HomeView into a StatefulWidget.
After that you need to modify updateDisplay into this:
Future<void> updateDisplay() async {
MyData myData = new MyData();
dataList.forEach((d) async {
d.changePercentage = await myData
.getDataPercentage("assets/data/" + d.fileName + ".xml");
print(d.dataName +
" change percentage: " +
d.changePercentage.toString() +
"%");
});
setState(() {});
}
By calling setState (() {}); you invoke the build method inside the StatefulWidget.
EDIT
Change your HomeView like this:
class HomeView extends StatefulWidget {
final List<MyDisplay> dataList = [
new MyDisplay("Sample Display",
"SampleFile", 0),
];
Future<void> updateDisplay() async {
MyData myData = new MyData();
dataList.forEach((d) async {
d.changePercentage = await myData
.getDataPercentage("assets/data/" + d.fileName + ".xml");
print(d.dataName +
" change percentage: " +
d.changePercentage.toString() +
"%");
});
}
#override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: new ListView.builder(
itemCount: widget.dataList.length,
itemBuilder: (BuildContext context, int index) =>
buildCard(context, index)),
);
}
Widget buildCard(BuildContext context, int index) {
return Container(
child: Card(
color: Colors.teal.shade200,
child: Padding(
padding: const EdgeInsets.all(25),
child: Column(children: <Widget>[
Text(widget.dataList[index].dataName,
style: new TextStyle(
fontSize: 20, fontWeight: FontWeight.bold)),
Text(widget.dataList[index].changePercentage.toString() + "%")
]))));
}
}
And your Home like this:
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomeState();
}
}
class _HomeState extends State<Home> {
int currentPageIndex = 0;
HomeView _homeView = HomeView();
List<Widget> pageWidgets = [_homeView, SecondaryPage(), SettingsPage()];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("My App"),
centerTitle: true,
),
body: pageWidgets[currentPageIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped,
currentIndex: currentPageIndex,
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.home), label: "Tab One"),
BottomNavigationBarItem(
icon: new Icon(Icons.account_balance), label: "Tab Two"),
BottomNavigationBarItem(
icon: new Icon(Icons.settings), label: "Tab Three"),
],
),
floatingActionButton: FloatingActionButton(onPressed: () {
// Update data from here
_homeView.updateDisplay();
}));
}
// Changes the index when the tapped page has loaded
void onTabTapped(int index) {
setState(() {
currentPageIndex = index;
});
}
}

Is there a way to display the BottomNavigationBar on every View?

I am trying to display the BottomNavigationBar on every View I have, it's working but this is a "dumb" way to do that...
I have a custom BottomNavigationBar which I am inserting in every View.
var selectedIndex = 0;
class CustomBottombar extends StatefulWidget {
CustomBottombar({Key key}) : super(key: key);
#override
_CustomBottombarState createState() => _CustomBottombarState();
}
class _CustomBottombarState extends State<CustomBottombar> {
List _viewList = [FirstView(), SecondView()];
#override
Widget build(BuildContext context) {
return BottomNavigationBar(
currentIndex: selectedIndex,
onTap: _onItemTapped,
items: _items,
);
}
void _onItemTapped(int index) {
setState(() {
selectedIndex = index;
Navigator.of(context).popUntil((route) => route.isFirst
);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => _viewList[index]),
);
});
}
final _items = [
BottomNavigationBarItem(
icon: Icon(
Icons.refresh,
color: Color(0xFFACACAC),
size: 35,
),
title: Text("first")),
BottomNavigationBarItem(
icon: Icon(
Icons.phone,
color: Color(0xFFACACAC),
size: 35,
),
title: Text("second"),
),
BottomNavigationBarItem(
icon: Icon(
Icons.add_shopping_cart,
color: Color(0xFFACACAC),
size: 35,
),
title: Text("thrid"),
),
];
}
in the _onItemTapped function I pop everything from the "Navigationstack" and then I am displaying the Screen that is in my Items.
in my FirstView() I have then this code
class FirstView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(""),
bottomNavigationBar: CustomBottombar(),
endDrawer: CustomDrawer(),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ContactView()),
);
},
child: Text('First'),
),
),
);
}
}
Now I want to move to "ContactView" which is not an Item in the BottomNavigationBar
class ContactState extends State<ContactView> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar("title"),
endDrawer: CustomDrawer(),
bottomNavigationBar: CustomBottombar(),
body: SafeArea(
bottom: true,
child: SingleChildScrollView(
child: Container(child: Text("Contact"),),
)),
);
}
}
I'll also have a lot of other views which are not in the items array but I want to display the BottomNavigationBar on.
My Issue is really this function.
void _onItemTapped(int index) {
setState(() {
selectedIndex = index;
Navigator.of(context).popUntil((route) => route.isFirst
);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => _viewList[index]),
);
});
}
because here I'm deleting the navigation history to display the View which is in the Items Array.
Is there a standard way to do this, Hopefully, someone can help.
EDIT:
For clarification: I have like 10 Screens. Only 3 of those are navigatiable via BottomNavigationBar, Let's say the first 3 of those 10. now I want to Navigate to Screen4 from Screen1. The navigationbar disappears on screen4. I want Want to keep the Navigationbar on all Screens.
Edit 2
#Dhaval Kansara answer worked for me but I got a new Problem.
I have an enddrawer, before the fix it was above the BottomNavigationBar now the BottomNavigationBar is above.
but I want it like this
Use CupertinoTabBar as shown below for the static BottomNavigationBar.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mqttdemo/Screen2.dart';
import 'package:mqttdemo/Screen3.dart';
import 'Screen1.dart';
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _currentIndex;
List<Widget> _children;
#override
void initState() {
_currentIndex = 0;
_children = [
Screen1(),
Screen2(),
Screen3(),
];
super.initState();
}
#override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
currentIndex: _currentIndex,
onTap: onTabTapped,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Screen 1"),
),
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Screen 2"),
),
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text("Screen 3")),
],
),
tabBuilder: (BuildContext context, int index) {
return CupertinoTabView(
builder: (BuildContext context) {
return SafeArea(
top: false,
bottom: false,
child: CupertinoApp(
home: CupertinoPageScaffold(
resizeToAvoidBottomInset: false,
child: _children[_currentIndex],
),
),
);
},
);
}
);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
}
Navigate to screen4 from Screen3 as shown below:
class Screen3 extends StatefulWidget {
#override
_Screen3State createState() => _Screen3State();
}
class _Screen3State extends State<Screen3> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.black,
child: Center(
child: RaisedButton(
child: Text("Click me"),
onPressed: () {
Navigator.of(context, rootNavigator: false).push(MaterialPageRoute(
builder: (context) => Screen4(), maintainState: false));
},
),
),
);
}
}