I have a bottom navigation bar and realized that the different pages/widgets that the navigator was going to were pretty much the exact same page (except for 2 parameters that changed). So instead of creating 2 pages/widgets which were pretty much identical (with only 2 differing parameters), I wanted to consolidate it into only one widget and pass the parameters from the page with the bottom navigator. The problem is that now that I did that it won't change the page it displays, or at least it won't change consistently (it usually will only show the page that corresponds to the first tab in the navigator (i.e., index = 0)). Here is my page with the bottom navigator:
class FreestylePage extends StatefulWidget {
const FreestylePage({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() {
return _FreestylePageState();
}
}
class _FreestylePageState extends State<FreestylePage> {
int _currentIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: showCategory(_currentIndex),
)),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.looks_one_outlined),
label: 'Single rope',
backgroundColor: Color.fromRGBO(204, 16, 138, 1)),
BottomNavigationBarItem(
icon: Icon(Icons.looks_two_outlined),
label: 'Double dutch',
backgroundColor: Color.fromRGBO(204, 16, 138, 1)),
],
onTap: (index) {
if (mounted) {
setState(() {
_currentIndex = index;
});
}
},
),
);
}
showCategory(index) {
if (index == 0) {
return [
WorkoutListPage(categoryIndex: 2, subCategories: Utils.srfDropdown)
];
} else {
return [
WorkoutListPage(categoryIndex: 3, subCategories: Utils.ddfDropdown)
];
}
}
}
And the WorkoutListPage looks as follows:
class WorkoutListPage extends StatefulWidget {
final int categoryIndex;
final List<String> subCategories;
const WorkoutListPage(
{Key? key, required this.categoryIndex, required this.subCategories})
: super(key: key);
#override
State<StatefulWidget> createState() {
return _WorkoutListPageState();
}
}
class _WorkoutListPageState extends State<WorkoutListPage> {
bool isLoading = true;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) =>
FutureBuilder<List<Map<String, dynamic>>>(
future: MyCard.getData(widget.categoryIndex, widget.subCategories)!
.whenComplete(() => setState(() {
isLoading = false;
})),
builder: ((context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
return FutureBuilder<List<MyCard>>(
future: MyCard.readData(snapshot.data),
builder: (context, cards) {
if (cards.hasData) {
final card = cards.data!;
return Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: card.length,
itemBuilder: (context, index) {
return MyCard.buildCard(card[index], context);
},
),
);
} else {
return const Text("No data");
}
});
} else {
return isLoading
? Column(
children: const [CircularProgressIndicator()],
)
: const Text("You do not have any workouts yet");
}
}),
);
}
This doesn't work, but ironically if I change my showCategory function in the widget with the bottom navigation bar to the following:
showCategory(index) {
if (index == 0) {
return [
WorkoutListPage(categoryIndex: 2, subCategories: Utils.srfDropdown)
];
} else {
return [const FreestyleDDPage()];
}
}
where the FreestyleDDPage is the following:
class FreestyleDDPage extends StatefulWidget {
const FreestyleDDPage({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() {
return _FreestyleDDPageState();
}
}
class _FreestyleDDPageState extends State<FreestyleDDPage> {
var isLoading = true;
#override
Widget build(BuildContext context) =>
FutureBuilder<List<Map<String, dynamic>>>(
future: MyCard.getData(3, Utils.ddfDropdown)!
.whenComplete(() => setState(() {
isLoading = false;
})),
builder: ((context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
return FutureBuilder<List<MyCard>>(
future: MyCard.readData(snapshot.data),
builder: (context, cards) {
if (cards.hasData) {
final card = cards.data!;
return Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: card.length,
itemBuilder: (context, index) {
return MyCard.buildCard(card[index], context);
},
),
);
} else {
return const Text("No data");
}
});
} else {
return isLoading
? Column(
children: const [CircularProgressIndicator()],
)
: const Text("You do not have any workouts yet");
}
}),
);
}
then it works.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
CustomWidgetWithParametr(index: 0 , categoryName: "HOME"),
CustomWidgetWithParametr(index: 1 , categoryName: "BUSINES"),
CustomWidgetWithParametr(index: 2 , categoryName: "SCHOOL"),
CustomWidgetWithParametr(index: 3 , categoryName: "Settings"),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
backgroundColor: Colors.red,
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
backgroundColor: Colors.green,
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
backgroundColor: Colors.purple,
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
backgroundColor: Colors.pink,
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
}
class CustomWidgetWithParametr extends StatefulWidget {
const CustomWidgetWithParametr({Key? key, required this.index, required this.categoryName}) : super(key: key);
final int index;
final String categoryName;
#override
State<CustomWidgetWithParametr> createState() => _CustomWidgetWithParametrState();
}
class _CustomWidgetWithParametrState extends State<CustomWidgetWithParametr> {
#override
Widget build(BuildContext context) {
return
Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(widget.index.toString()),
Text(widget.categoryName),
],
);
}
}
Related
I want to rebuild just one widget in IndexedStack.
my app has 5 widgets(PostListScreen,AgendaListScreen ....)in IndexedStack.
PostListSceen have PostContainer.
I can go to PostContainer when I click element in PostListScreen. and then go back to PostListScreen.
5 widgets are all rebuild. I think just PostListScreen widget have to rebuild.
why other widgets also rebuild????. It makes unnecessary communication with server.
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
PreferredSizeWidget? _appBarContainer(bottomSelectedIndex) {
return (bottomSelectedIndex == 0 || bottomSelectedIndex == 1)
? const AppBarRedContainer()
: AppBarNavigatorContainer(actions: [
IconButton(
padding: EdgeInsets.only(right: 15.0.w),
icon: SvgPicture.asset(
"assets/images/notification_b.svg",
),
iconSize: 24.0.r,
constraints: const BoxConstraints(),
onPressed: () {
Beamer.of(context).beamToNamed('/$locationAlarm');
},
)
], title: (bottomSelectedIndex == 3 ? "핫글 모음" : "내 정보"))
as PreferredSizeWidget;
}
#override
Widget build(BuildContext context) {
int bottomSelectedIndex =
Provider.of<IndexNotifier>(context, listen: false).bottomSelectedIndex;
StockModel stock = Provider.of<StockCodeNotifier>(context, listen: false).stock;
if (stock.code == null) {
Provider.of<StockCodeNotifier>(context, listen: false).getStocks();
stock = Provider.of<StockCodeNotifier>(context, listen: false).stock;
FlutterNativeSplash.remove();
}
return Scaffold(
appBar: _appBarContainer(bottomSelectedIndex),
body: SafeArea(
child: IndexedStack(
index: bottomSelectedIndex,
children: [
PostListScreen(stock: stock),
AgendaListScreen(
stock: stock,
),
WriteScreen(
stockCode: stock.code!,
),
const HotListScreen(),
const MyInfoScreen()
],
),
),
);
}
}
postListScreen.dart
class PostListScreen extends StatefulWidget {
const PostListScreen({Key? key, required this.stock}) : super(key: key);
final StockModel stock;
#override
State<PostListScreen> createState() => _PostListScreenState();
}
class _PostListScreenState extends State<PostListScreen> {
late Future<List<PostModel>> _postData;
bool init = false;
#override
void initState() {
if(!init){
_postData = _fetchPostData();
init = true;
}
super.initState();
}
Future<List<PostModel>> _fetchPostData() async {
return await PostListService().getPosts(widget.stock.code!);
}
Future<void> _onRefresh() async {
setState(() {});
Future.value(null);
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<PostModel>>(
future: _postData,
builder: (context, snapshot) {
return RefreshIndicator(
onRefresh: _onRefresh,
child: CustomScrollView(slivers: [
SliverAppBar(
floating: true,
expandedHeight: 148.0.h,
flexibleSpace: FlexibleSpaceBar(
background: StockInfo(
stockCount: widget.stock.count!,
stockName: widget.stock.name!,
),
),
),
(snapshot.hasData == false)
? const SliverFillRemaining(
child: Center(child: CircularProgressIndicator()))
: (snapshot.data!.isNotEmpty)
? _sliverList(snapshot.data!)
: SliverFillRemaining(
child: NoContentContainer(
onRefresh: _onRefresh,
noText: "이야기가 없어요",
offerText: '첫 이야기를 작성해 보시는 건 어떨까요?'),
),
]),
);
});
}
}
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,
),
]),
),
);
})),
);
}
}
I'm generating TextFormFields dynamically and assigning unique TextEditingControllers individually. I then only update the text of the TextFormField that's currently in focus
Column textField(int n) {
List<Widget> listForm = [];
while (n > 0) {
var textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: textEditingController,
onTap: () {
debugPrint('Current Controller: $textEditingController');
setState(() {
_selectedField = textEditingController;
});
},
),
);
n--;
}
return Column(children: listForm);
}
with
InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
/// On tap is able to fetch the correct active TextFormField
debugPrint('Active field: $_selectedField');
_selectedField!.text = 'Hello!'; // doesn't work
setState(() {
/// Setting TextEditingController.text doesn't work
_selectedField!.text = 'Item $index'; // doesn't work
});
}
},
I'm able to successfully fetch the TextEditingController, but unable to update their text. Any idea why TextEditingController.text doesnt work?
Minimal repro
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController? _selectedField;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(flex: 1, child: textField(3)),
Expanded(flex: 1, child: listItems()),
],
),
),
),
);
}
Column textField(int n) {
List<Widget> listForm = [];
while (n > 0) {
var textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: textEditingController,
onTap: () {
debugPrint('Current Controller: $textEditingController');
setState(() {
_selectedField = textEditingController;
});
},
),
);
n--;
}
return Column(children: listForm);
}
ListView listItems() {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
/// On tap is able to fetch the correct active TextFormField
debugPrint('Active field: $_selectedField');
_selectedField!.text = 'Hello!'; // doesn't work
setState(() {
/// Setting TextEditingController.text doesn't work
_selectedField!.text = 'Item $index'; // doesn't work
});
}
},
child: ListTile(
title: Text('Item $index'),
),
);
},
);
}
}
TextEditingValue() will work:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController? _selectedField = TextEditingController();
List<Widget> listForm = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(flex: 1, child: textField(3)),
Expanded(flex: 1, child: listItems()),
],
),
),
),
);
}
Column textField(int n) {
while (n > 0) {
TextEditingController _textEditingController = TextEditingController();
listForm.add(
TextFormField(
controller: _textEditingController,
onTap: () {
_selectedField = _textEditingController;
debugPrint( 'selected' + _selectedField!.value.text );
debugPrint('main' + _textEditingController.toString());
},
),
);
n--;
}
return Column(children: listForm);
}
ListView listItems() {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
debugPrint('Selected $index!');
if (_selectedField != null) {
_selectedField!.value =
TextEditingValue(text: 'Item $index'); // doesn't work
debugPrint(_selectedField?.value.text);
debugPrint(_selectedField.hashCode.toString());
debugPrint('Item $index');
}
},
child: ListTile(
title: Text('Item $index'),
),
);
},
);
}
}
I have 4 Pages a page view with their respective bottom navigator, At the first Index, I'm executing a function that navigates to the 4th page. However, after navigation, The bottom nav disappears, which isn't consistent. How do I make it consistent..,
class HomePage extends StatefulWidget {
HomePage({
Key key,
this.category,
this.shopname,
}) : super(key: key); //update this to include the uid in the constructor
final String shopname;
final DocumentSnapshot category;
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Page controller,
PageController _tabsPageController;
int _selectedTab = 0;
#override
void initState() {
_tabsPageController = PageController();
super.initState();
}
#override
void dispose() {
_tabsPageController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: PageView(
controller: _tabsPageController,
onPageChanged: (num) {
setState(() {
_selectedTab = num;
});
},
children: [
Navigator at landing page
LandingPage(), //<<================
SavedTab(
shopname: widget.shopname,
),
MyDrawer(),
Navigates to HomePage()
HomeTab() //<<================
],
),
),
BottomTabs(
selectedTab: _selectedTab,
tabPressed: (num) {
_tabsPageController.animateToPage(num,
duration: Duration(milliseconds: 300),
curve: Curves.easeOutCubic);
},
),
],
),
);
}
}
You actually just need the Bottom Navigator Bar that routes to the desired pages. It basically renders all the screens inside the BottomNavBar widget so the Bottom Navigation Bar is always showing. Here is an example:
class BottomNavBar extends StatefulWidget {
#override
_BottomNavBarState createState() => _BottomNavBarState();
}
class _BottomNavBarState extends State<BottomNavBar> {
int _selectedIndex = 0;
static List<Widget> _widgetOptions = <Widget>[
Screen1(),
Screen2(),
Screen3(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.dashboard),
label: 'Screen 1',
),
BottomNavigationBarItem(
icon: Icon(Icons.work),
label: 'Screen 2',
),
BottomNavigationBarItem(
icon: Icon(Icons.directions_car),
label: 'Screen 3',
),
],
currentIndex: _selectedIndex,
onTap: _onItemTapped,
),
);
}
}
Let me know in the comments if it works for you.
Check my code
https://github.com/hoc081098/Movie-Ticket-Booking/blob/master/MobileApp/datn/lib/ui/app_scaffold.dart
or
https://github.com/hoc081098/nested_navigation
Custom Scaffold
class AppScaffold extends StatefulWidget {
final List<BottomNavigationBarItem> items;
final List<AppScaffoldWidgetBuilder> builders;
const AppScaffold({
Key? key,
required this.items,
required this.builders,
}) : super(key: key);
#override
_AppScaffoldState createState() => _AppScaffoldState();
static NavigatorState navigatorOfCurrentIndex(
BuildContext context, {
AppScaffoldIndex? switchToNewIndex,
}) {
final appScaffoldState =
context is StatefulElement && context.state is _AppScaffoldState
? context.state as _AppScaffoldState
: context.findAncestorStateOfType<_AppScaffoldState>()!;
final currentIndex = appScaffoldState.currentIndex;
final navigatorKeys = appScaffoldState.navigatorKeys;
final newIndex = switchToNewIndex?.rawValue;
if (newIndex != null &&
newIndex != currentIndex &&
appScaffoldState.mounted) {
appScaffoldState.onTap(newIndex);
return navigatorKeys[newIndex].currentState!;
}
return navigatorKeys[currentIndex].currentState!;
}
static NotReplayValueStream<AppScaffoldIndex> currentIndexStream(
BuildContext context) =>
context.findAncestorStateOfType<_AppScaffoldState>()!.indexS;
static NavigatorState navigatorByIndex(
BuildContext context,
AppScaffoldIndex index,
) {
final appScaffoldState =
context.findAncestorStateOfType<_AppScaffoldState>()!;
return appScaffoldState.navigatorKeys[index.rawValue].currentState!;
}
}
class _AppScaffoldState extends State<AppScaffold> with DisposeBagMixin {
var navigatorKeys = <GlobalKey<NavigatorState>>[];
final indexS = ValueSubject(AppScaffoldIndex.home, sync: true);
#pragma('vm:prefer-inline')
#pragma('dart2js:tryInline')
int get currentIndex => indexS.requireValue.rawValue;
#override
void initState() {
super.initState();
navigatorKeys = List.generate(
widget.builders.length,
(_) => GlobalKey<NavigatorState>(),
);
indexS.disposedBy(bag);
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
final navigatorState = navigatorKeys[currentIndex].currentState!;
final canPop = navigatorState.canPop();
if (canPop) {
navigatorState.maybePop();
}
if (!canPop && currentIndex > 0) {
onTap(0);
return Future.value(false);
}
return Future.value(!canPop);
},
child: RxStreamBuilder<AppScaffoldIndex>(
stream: indexS,
builder: (context, snapshot) {
final index = snapshot!.rawValue;
return Scaffold(
body: buildBody(index),
bottomNavigationBar: BottomNavigationBar(
items: widget.items,
type: BottomNavigationBarType.fixed,
currentIndex: index,
onTap: onTap,
),
);
},
),
);
}
void onTap(final int newIndex) {
if (currentIndex == newIndex) {
navigatorKeys[currentIndex]
.currentState
?.popUntil((route) => route.isFirst);
} else {
indexS.add(_fromRawValue(newIndex));
}
}
Widget buildBody(int index) {
return IndexedStack(
index: index,
children: [
for (int i = 0; i < widget.builders.length; i++)
Navigator(
key: navigatorKeys[i],
onGenerateRoute: (settings) => MaterialPageRoute(
settings: settings,
builder: (context) => widget.builders[i](context, settings),
),
observers: [
HeroController(),
],
)
],
);
}
}
Home page
return AppScaffold(
key: appScaffoldKey,
builders: [
(context, settings) => homeRoutes[settings.name]!(context, settings),
(context, settings) =>
favoritesRoutes[settings.name]!(context, settings),
(context, settings) =>
notificationsRoutes[settings.name]!(context, settings),
(context, settings) => profileRoutes[settings.name]!(context, settings),
],
items: [
BottomNavigationBarItem(
icon: const Icon(Icons.home_rounded),
label: S.of(context).home,
),
BottomNavigationBarItem(
icon: const Icon(Icons.favorite_rounded),
label: S.of(context).favorites,
),
BottomNavigationBarItem(
icon: const Icon(Icons.notifications),
label: S.of(context).notifications,
),
BottomNavigationBarItem(
icon: const Icon(Icons.person_rounded),
label: S.of(context).profile,
),
],
);
I would like to sort a ListView based on a StreamController in a BottomNavigationBar.
The problem is that the Listview doesn't refresh when I click the button on the app bar.
I would like to pass as parameter the function (Comparable of Company) which the user chooses.
Here's my code:
Home.dart
final CompanyService _companyService = CompanyService();
final AuthService _auth = AuthService();
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
Home createState() => Home();
}
class Home extends State<HomePage> {
Comparator<Company> _sortFunction;
int _currentIndex;
var _tabs = [];
#override
void initState() {
super.initState();
_currentIndex = 0;
_sortFunction = (a, b) => a.name.compareTo(b.name);
}
PreferredSize getDoubleHomeAppBar() {
return PreferredSize(
preferredSize: Size.fromHeight(55.0),
child: Row(
children: <Widget>[
Expanded(
child: Container(
padding: EdgeInsets.only(left: 12.0),
margin:
const EdgeInsets.only(left: 12.0, bottom: 8.0, right: 12.0),
color: PRIMARY_COLOR,
),
),
FlatButton.icon(
icon: Icon(Icons.sort_by_alpha),
label: Text(
'Sort',
),
onPressed: () {
setState(() {
_sortFunction = (a, b) => b.city.compareTo(a.city);
_tabs[0] = CompanyTab(sortFunction: _sortFunction);
});
},
),
],
));
}
#override
Widget build(BuildContext context) {
_tabs = [
CompanyTab(sortFunction: _sortFunction),
MapTab(),
Center(child: Text('Profile')),
Center(child: Text('Settings')),
];
return Scaffold(
backgroundColor: BACKGROUND_COLOR,
appBar: AppBar(
elevation: 0.0,
centerTitle: true,
title: Text(
'JobAdvisor',
style: TextStyle(
fontSize: MediaQuery.of(context).size.height / 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
bottom: _currentIndex == 0 ? getDoubleHomeAppBar() : null,
actions: <Widget>[...],
),
body: _tabs[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
backgroundColor: BACKGROUND_COLOR,
type: BottomNavigationBarType.fixed,
items: [
...
],
onTap: (index) {
setState(() {
_currentIndex = index;
print('Index: $index');
print('Current index: $_currentIndex');
});
},
),
);
}
}
CompanyTab.dart
#immutable
class CompanyTab extends StatefulWidget {
final CompanyService _companyService = CompanyService();
final Comparator<Company> sortFunction;
CompanyTab({Key key, this.sortFunction}) : super(key: key);
#override
_CompanyTabState createState() =>
_CompanyTabState(_companyService, sortFunction);
}
class _CompanyTabState extends State<CompanyTab> {
StreamController<List<Company>> _streamController;
final CompanyService _companyService;
Comparator<Company> sortFunction;
_CompanyTabState(this._companyService, this.sortFunction);
#override
void initState() {
super.initState();
}
StreamBuilder companyList() {
return StreamBuilder<List<Company>>(
initialData: [],
stream: _streamController.stream,
builder: (BuildContext context, AsyncSnapshot<List<Company>> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.connectionState == ConnectionState.waiting ||
snapshot.connectionState == ConnectionState.none ||
snapshot.data == null) {
return LoadingWidget();
} else {
return ListView.builder(
padding: const EdgeInsets.all(10),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
Company company = snapshot.data.elementAt(index);
...
}
}
});
}
#override
void dispose() {
_streamController.close();
super.dispose();
}
void _getData() {
_streamController = new StreamController<List<Company>>();
_companyService.getCompanies().then((value) => {_elaborateList(value)});
}
void _elaborateList(List<Company> list) {
List<Company> tmp = list;
tmp.sort(sortFunction);
print(tmp.toString());
_streamController.sink.add(tmp);
}
#override
Widget build(BuildContext context) {
_getData();
return Center(
child: Container(
child: companyList(),
),
);
}
}
I think the problem is in your sort method. It should be like this:
_sortFunction.sort((a, b) => a.name.compareTo(b.name));
You can read from the official document.
EDIT:
And you need to use sortFunction of the widget in here:
tmp.sort(widget.sortFunction);
You are not using the same sortFunction in CompanyTab. You should use the sortFunction which comes as a parameter. Here is a blog about it.