StreamBuilder not updating widget - flutter

i have a Widget that has an AppBar with a progress bar, and a PageView with 4 pages, when moving between pages i am increasing / decreasing the progress bar.
I'm trying to do all the logic in my ViewModel.
This is my ViewModel (omitted non relevant stuff):
class RegisterViewModel extends BaseViewModel with RegisterViewModelInputs, RegisterViewModelOutputs {
final StreamController _sexStreamController = StreamController<int>.broadcast();
final StreamController _progressBarController = StreamController<double>.broadcast();
final StreamController _currentIndexController = StreamController<int>.broadcast();
final StreamController _isBackEnabled = StreamController<bool>.broadcast();
double _progress = 0.25;
int _index = 0;
#override
setCurrentIndex(int index) {
currentIndex.add(index);
}
#override
increaseProgress() {
if (_progress <= 1.0) {
_progress += 0.25;
progress.add(_progress);
}
}
#override
decreaseProgress() {
if (_progress > 0) {
_progress -= 0.25;
progress.add(_progress);
}
}
#override
setIsBackEnabled(int index) {
_isBackEnabled.add(index > 0 ? true : false);
}
#override
nextPage() {
if (_index < 4) {
_index++;
increaseProgress();
setCurrentIndex(_index);
}
}
#override
previousPage() {
if (_index > 0) {
_index--;
decreaseProgress();
setCurrentIndex(_index);
}
}
#override
Sink get currentIndex => _currentIndexController.sink;
#override
Sink get progress => _progressBarController.sink;
#override
Sink get isBackEnabled => _isBackEnabled.sink;
#override
Stream<int> get outputCurrentIndex => _currentIndexController.stream.map((currentIndex) => currentIndex);
#override
Stream<double> get outputProgress => _progressBarController.stream.map((progress) => progress);
#override
Stream<bool> get outputIsBackEnabled => outputIsBackEnabled.map((isEnabled) => isEnabled);
}
And here is my View:
class RegisterView extends StatefulWidget {
const RegisterView({Key? key}) : super(key: key);
#override
_RegisterViewState createState() => _RegisterViewState();
}
class _RegisterViewState extends State<RegisterView> {
final RegisterViewModel _viewModel = getIt<RegisterViewModel>();
final PageController _pageController = PageController(initialPage: 0);
final FixedExtentScrollController _weightScrollController = FixedExtentScrollController(initialItem: 80);
final FixedExtentScrollController _ageScrollController = FixedExtentScrollController(initialItem: 13);
final FixedExtentScrollController _heightScrollController = FixedExtentScrollController(initialItem: 13);
#override
void initState() {
_bind();
super.initState();
}
#override
void dispose() {
_viewModel.dispose();
super.dispose();
}
_bind() {
_viewModel.start();
}
#override
Widget build(BuildContext context) {
_viewModel.outputCurrentIndex.listen((index) {
_pageController.animateToPage(index, duration: const Duration(milliseconds: 1000), curve: Curves.ease);
});
List<Widget> pagesList = [
SexPage(
onConfirm: (sex) {
_viewModel.setSex(sex);
_viewModel.nextPage();
},
),
AgePage(
scrollController: _ageScrollController,
),
WeightPage(scrollController: _weightScrollController),
HeightPage(scrollController: _heightScrollController),
];
return Scaffold(
backgroundColor: ColorManager.backgroundColor,
appBar: AppBar(
systemOverlayStyle: SystemUiOverlayStyle(
statusBarColor: ColorManager.backgroundColor,
statusBarBrightness: Brightness.dark,
statusBarIconBrightness: Brightness.dark,
),
centerTitle: true,
title: AppBarWidget(_pageController),
elevation: AppSize.s0,
),
body: PageView(
reverse: true,
controller: _pageController,
physics: NeverScrollableScrollPhysics(),
children: [...pagesList],
),
);
}
}
class AppBarWidget extends StatelessWidget {
final PageController pageController;
final RegisterViewModel _viewModel = getIt<RegisterViewModel>();
AppBarWidget(
this.pageController, {
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
_viewModel.outputCurrentIndex.listen((index) {
pageController.animateToPage(index, duration: const Duration(milliseconds: 1000), curve: Curves.ease);
});
return Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
flex: 1,
child: InkWell(
child: Text(
AppStrings.skip,
style: Theme.of(context).textTheme.labelMedium,
),
onTap: () => _viewModel.nextPage(),
),
),
Expanded(
flex: 4,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: AppPadding.p60),
child: Transform(
alignment: Alignment.center,
transform: Matrix4.rotationY(pi),
child: StreamBuilder<double>(
stream: _viewModel.outputProgress,
builder: (context, snapshot) {
return Progresso(
progress: snapshot.data ?? 0,
progressStrokeCap: StrokeCap.round,
backgroundStrokeCap: StrokeCap.round,
progressColor: ColorManager.primary,
backgroundColor: ColorManager.progressBarBackgroundGrey,
progressStrokeWidth: 10.0,
backgroundStrokeWidth: 10.0,
);
}),
),
),
),
StreamBuilder<int>(
stream: _viewModel.outputCurrentIndex,
builder: (context, snapshot) {
return Expanded(
flex: 1,
child: (snapshot.data ?? 0) > 0
? InkWell(
child: Row(
children: [
Text(
AppStrings.back,
style: Theme.of(context).textTheme.labelMedium,
),
Icon(
Icons.arrow_forward_ios,
color: ColorManager.subtitleGrey,
),
],
),
onTap: () => _viewModel.previousPage(),
)
: Container(),
);
}),
],
);
}
}
When i'm calling _viewModel.previousPage() & _viewModel.previousPage()` from the AppBarWidget, the progress bar view is updated, and there is a scroll animation to the next page.
But for some reason if the onConfirm callback:
onConfirm: (sex) {
_viewModel.setSex(sex);
_viewModel.nextPage();
}
is called from within SexPage, the scroll animation is working, but the progress bar view and the isBackEnabled is not updating.
I have checked and a new value is being added to the _progressBarController sink, but for some reason the StreamBuilder does not receive it? same for the isBackEnabled stream..
What am i doing wrong?
And another question i have is where should I listen to the outputCurrentIndex stream, and call _pageController.animateToPage()?

Apparently i had an issue with my Dependency Injection.
I'm using get_it and i used registerFactory, instead of registerLazySingleton.
Which probably made me have 2 separate ViewModels in each widget.

Related

AnimatedList unnecessary rebuilds

I'm animating the removal and addition of items to my list using an AnimatedList whose data is from a firestore stream.
I use riverpod to manage my state and noticed my List state rebuilds whenever I navigator to another screen. It also rebuilds on hot reload.
StateNotifier class for Nav bar
class NavigationState extends StateNotifier<Widget> {
NavigationState() : super(_homeScreen);
static const HomeScreen _homeScreen = HomeScreen();
static const SettingScreen _settingScreen = SettingScreen();
static Screens _page = Screens.home;
Screens get page => _page;
void selectPage(Screens page) {
switch (page) {
case Screens.home:
_page = Screens.home;
state = _homeScreen;
break;
case Screens.settings:
_page = Screens.settings;
state = _settingScreen;
}
}
}
Nav bar class
class CustomNav extends ConsumerWidget {
const CustomNav({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final navIndex = ref.watch(navigationStateProvider.notifier);
return AnimatedContainer(
key: UniqueKey(),
curve: Curves.ease,
duration: const Duration(milliseconds: 1000),
padding: const EdgeInsets.only(top: 5),
height: 58,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
NavigationIcon(
icon: "assets/svg/home.svg",
color: navIndex.page == Screens.home
? Colors.green
: const Color.fromRGBO(133, 144, 132, 1),
label: "Home",
onPressed: () {
ref.read(navigationStateProvider.notifier).selectPage(
Screens.home);
},
),
NavigationIcon(
key: UniqueKey(),
icon: "assets/svg/setting.svg",
color: navIndex.page == Screens.settings
? Colors.green
: const Color.fromRGBO(133, 144, 132, 1),
label: "Settings",
onPressed: () {
ref
.read(navigationStateProvider.notifier)
.selectPage(Screens.settings);
}),
],
),
);
}
}
Home Screen
class HomeScreen extends ConsumerStatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
ConsumerState<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends ConsumerState<HomeScreen>
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
late final TabController _tabController;
final List<UserProperties> _firebaseList = [];
final pref = Preferences();
final GlobalKey<AnimatedListState> _key = GlobalKey();
#override
void initState() {
_tabController = TabController(length: 2, vsync: this);
super.initState();
updateKeepAlive();
}
void _addItem(List<QueryDocumentSnapshot> data) {
for (var element in data) {
final userProperties =
UserProperites.fromJson(element.data() as Map<String, dynamic>);
if (!_firebaseList.contains(userProperties)) {
_firebaseList.add(userProperties);
final itemIndex = data.indexOf(element);
_key.currentState
?.insertItem(itemIndex, duration: const Duration(seconds: 5));
}
}
}
#override
void dispose() {
super.dispose();
_tabController.dispose();
}
#override
Widget build(BuildContext context) {
super.build(context);
final selectedIndex = ref.watch(tabIndexProvider.notifier);
final checkBoxState = ref.watch(mainCheckBoxProvider.state);
_tabController.addListener(() {
checkBoxState.update((state) => false);
selectedIndex.state = _tabController.index;
});
final userId = pref.pref.getString("userId");
return Scaffold(
key: UniqueKey(),
body: Column(
key: UniqueKey(),
children: [
const SizedBox(
height: 15,
),
Expanded(
child: StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
key: UniqueKey(),
stream: FirebaseFirestore.instance
.collection("users")
.doc(userId!)
.collection("properties")
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Container();
}
var data = snapshot.data!.docs;
_addItem(data);
return AnimatedList(
padding: const EdgeInsets.only(bottom: 100),
key: _key,
initialItemCount: _firebaseList.length,
itemBuilder: (context, index, animation) {
final userPro = _firebaseList.elementAt(index);
return Padding(
padding: const EdgeInsets.all(2.0),
child: GestureDetector(
onTap: () => Navigator.pushNamed(
context, "details"),
child: PropertyTile(
key: ObjectKey(index),
image: user.image,
name: user.name),
),
),
);
});
}),
),
],
),
);
}
#override
bool get wantKeepAlive => true;
}
I've been on this issue for a while, that's part of the reason UniqueKey() is littered everywhere.
I thought of using Provider.select, but I don't really know what to select since I'm using an enum.
Prior to AnimatedList, everything works fine, and no unnecessary rebuilds.

How to make simple carousel only use PageView flutter?

How to make a simple carousel only using PageView flutter? because I need to know how to make a carousel using PageController.animeToPage on flutter, this is available documentation https://docs.flutter.dev/cookbook/animation/page-route-animation
import 'dart:async';
import 'package:flutter/material.dart';
class SignInPage extends StatefulWidget {
static const routeName = "/sign-in";
const SignInPage({Key? key}) : super(key: key);
#override
State<SignInPage> createState() => _SignInPageState();
}
class _SignInPageState extends State<SignInPage> {
int _selectedPage = 0;
late final _pageController = PageController(initialPage: _selectedPage);
late final Timer? _timer;
List<int> get _pageItem => [0, 1, 2, 3];
#override
void initState() {
super.initState();
_timer = Timer.periodic(Duration(seconds: 3), (time) async {
final nexPage = (_pageController.page?.toInt() ?? 0) + 1;
await _pageController.animateToPage(
nexPage,
duration: Duration(seconds: 1),
curve: Curves.easeOut,
);
});
}
#override
void dispose() {
super.dispose();
_pageController.dispose();
_timer?.cancel();
}
void _pageChanged(int currentPage) {
print("current page $currentPage");
_selectedPage = currentPage % _pageItem.length;
print("selected page $_selectedPage");
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: PageView.builder(
controller: _pageController,
onPageChanged: _pageChanged,
itemBuilder: (context, index) {
return Center(
child: Text(_pageItem[_selectedPage].toString()),
);
},
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
...List.generate(
_pageItem.length,
(index) => Icon(
Icons.circle,
color: index == _selectedPage ? Colors.blue : Colors.white,
),
),
],
),
Padding(
padding: const EdgeInsets.all(16),
child: ElevatedButton(
child: Text(
"Button",
),
onPressed: () async {
},
),
),
],
),
),
);
}
}
from this code you only use PageView, PageController, and Timer Periodic for make it carousel

Triggering page controller animation from ViewModel

i'm trying to implement a View with a PageView widget, that is being controlled by the ViewModel.
In my view i have buttons that trigger the following method in the ViewModel:
#override
nextPage() {
if (_index < 4) {
_index++;
increaseProgress();
currentIndex.add(_index);
}
}
currentIndex is just a sink of my _currentIndexController, and outputCurrentIndex is a stream of the controller:
#override
Sink get currentIndex => _currentIndexController.sink;
#override
Stream<int> get outputCurrentIndex => _currentIndexController.stream.map((currentIndex) => currentIndex);
it looks like the value is added to the stream successfully, but i can't get it to trigger changing pages, i've set up this listener in the initState() method of the view:
_viewModel.outputCurrentIndex.listen((index) {
_pageController.animateToPage(index, duration: const Duration(milliseconds: 1000), curve: Curves.ease);
});
but it is not triggered for some reason.. what am i doing wrong?
here is a full code of my View:
class RegisterView extends StatefulWidget {
const RegisterView({Key? key}) : super(key: key);
#override
_RegisterViewState createState() => _RegisterViewState();
}
class _RegisterViewState extends State<RegisterView> {
final RegisterViewModel _viewModel = getIt<RegisterViewModel>();
final PageController _pageController = PageController(initialPage: 0);
final FixedExtentScrollController _weightScrollController = FixedExtentScrollController(initialItem: 80);
final FixedExtentScrollController _ageScrollController = FixedExtentScrollController(initialItem: 13);
final FixedExtentScrollController _heightScrollController = FixedExtentScrollController(initialItem: 13);
#override
void initState() {
_bind();
super.initState();
}
#override
void dispose(){
_viewModel.dispose();
super.dispose();
}
_bind() {
_viewModel.start();
_viewModel.outputCurrentIndex.listen((index) {
_pageController.animateToPage(index, duration: const Duration(milliseconds: 1000), curve: Curves.ease);
});
}
#override
Widget build(BuildContext context) {
List<Widget> pagesList = [
const SexPage(),
AgePage(
scrollController: _ageScrollController,
),
WeightPage(scrollController: _weightScrollController),
HeightPage(scrollController: _heightScrollController),
];
return MultiProvider(
providers: [
StreamProvider.value(value: _viewModel.outputProgress, initialData: 0.25),
StreamProvider.value(value: _viewModel.outputCurrentIndex, initialData: 0),
],
child: Scaffold(
backgroundColor: ColorManager.backgroundColor,
appBar: AppBar(
systemOverlayStyle: SystemUiOverlayStyle(
statusBarColor: ColorManager.backgroundColor,
statusBarBrightness: Brightness.dark,
statusBarIconBrightness: Brightness.dark,
),
centerTitle: true,
title: AppBarWidget(),
elevation: AppSize.s0,
),
body: PageView(
reverse: true,
controller: _pageController,
physics: NeverScrollableScrollPhysics(),
children: [...pagesList],
),
),
);
}
}
class AppBarWidget extends StatefulWidget {
const AppBarWidget({
Key? key,
}) : super(key: key);
#override
State<AppBarWidget> createState() => _AppBarWidgetState();
}
class _AppBarWidgetState extends State<AppBarWidget> {
final RegisterViewModel _viewModel = getIt<RegisterViewModel>();
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
flex: 1,
child: InkWell(
child: Text(
AppStrings.skip,
style: Theme.of(context).textTheme.labelMedium,
),
onTap: () => _viewModel.nextPage(),
),
),
Expanded(
flex: 4,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: AppPadding.p60),
child: Transform(
alignment: Alignment.center,
transform: Matrix4.rotationY(pi),
child: Progresso(
progress: Provider.of<double>(context),
progressStrokeCap: StrokeCap.round,
backgroundStrokeCap: StrokeCap.round,
progressColor: ColorManager.primary,
backgroundColor: ColorManager.progressBarBackgroundGrey,
progressStrokeWidth: 10.0,
backgroundStrokeWidth: 10.0,
),
),
),
),
Expanded(
flex: 1,
child: Provider.of<int>(context) > 1
? InkWell(
child: Row(
children: [
Text(
AppStrings.back,
style: Theme.of(context).textTheme.labelMedium,
),
Icon(
Icons.arrow_forward_ios,
color: ColorManager.subtitleGrey,
),
],
),
onTap: () => _viewModel.previousPage(),
)
: Container(),
),
],
);
}
}
and my ViewModel:
class RegisterViewModel extends BaseViewModel with RegisterViewModelInputs, RegisterViewModelOutputs {
final StreamController _progressBarController = StreamController<double>.broadcast();
final StreamController _currentIndexController = StreamController<int>.broadcast();
final StreamController _isBackEnabled = StreamController<bool>.broadcast();
double _progress = 0.25;
int _index = 0;
#override
void dispose() {
_progressBarController.close();
_currentIndexController.close();
_isBackEnabled.close();
}
#override
void start() {
// TODO: implement start
}
#override
Sink get currentIndex => _currentIndexController.sink;
#override
Stream<int> get outputCurrentIndex => _currentIndexController.stream.map((currentIndex) => currentIndex);
#override
Stream<double> get outputProgress => _progressBarController.stream.map((progress) => progress);
#override
Sink get progress => _progressBarController.sink;
#override
setCurrentIndex(int index) {
currentIndex.add(index);
}
#override
increaseProgress() {
if (_progress <= 1.0) {
_progress += 0.25;
progress.add(_progress);
}
}
#override
decreaseProgress() {
if (_progress > 0) {
_progress -= 0.25;
progress.add(_progress);
}
}
#override
Sink get isBackEnabled => _isBackEnabled.sink;
#override
Stream<bool> get outputIsBackEnabled => outputIsBackEnabled.map((isEnabled) => isEnabled);
#override
setIsBackEnabled(int index) {
_isBackEnabled.add(index > 0 ? true : false);
}
#override
nextPage() {
if (_index < 4) {
_index++;
increaseProgress();
setCurrentIndex(_index);
}
}
#override
previousPage() {
if (_index > 0) {
_index--;
decreaseProgress();
setCurrentIndex(_index);
}
}
}
abstract class RegisterViewModelInputs {
register();
increaseProgress();
decreaseProgress();
nextPage();
previousPage();
setIsBackEnabled(int index);
Sink get currentIndex;
Sink get isBackEnabled;
Sink get progress;
}
abstract class RegisterViewModelOutputs {
Stream<int> get outputCurrentIndex;
Stream<double> get outputProgress;
Stream<bool> get outputIsBackEnabled;
}
EDIT: I have moved the stream listener to the _AppBarWidgetState build method, and it seems to work now, but i don't fully understand why it hasn't worked before..
is it because the PageController wasn't assigned to a view yet? where is the correct place for the listener? it doesn't makes sense to me for it to be in a child widget.

Flutter: Having a problem with Custom Drawer and internal navigation on fab pressed

I am trying to build a custom drawer, just to practice while I study flutter and collecting ideas from videos I see on the web.
In the app I am trying to make, I use Provider and Navigator 2.0 (Router) and it has been not easy so far to implement Navigator 2.
The as mentioned is made as a Stack that has as children the DrawerComponent and the Specific page handled by a DrawerManager and an AppRouter.
In the main page I have a Floating Action Button that on pressed does an effect transitioning from the list of Todos to the AddTodo Screen like a circle from the bottom corner revealing the new page.
like this image shows
The problem is basically that whenever I press the FAB the animation works, but I do not want to see the Drawer in full page like that is the page from where I am transitioning. Instead I would like to see a transition from the TodoList page to the AddTodo Page.
Here the code:
class _MainPageState extends State<MainPage> {
final _drawerManager = DrawerManager();
late AppRouter _appRouter;
#override
void initState() {
super.initState();
_appRouter = AppRouter(drawerManager: _drawerManager);
}
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => _drawerManager),
Provider<ITodoRepository>(
lazy: false,
create: (_) =>
TodoRepository(db: Provider.of<HeyTaskDatabase>(context)),
)
],
child: Consumer<DrawerManager>(
builder: (context, drawerManager, child) {
return MaterialApp(
home: Scaffold(
backgroundColor: const Color.fromARGB(255, 2, 9, 38),
body: Stack(children: [
const DrawerComponent(),
WillPopScope(
onWillPop: () async {
if (drawerManager.isDrawerOpen) {
drawerManager.closeDrawer();
return false;
} else {
return true;
}
},
child: GestureDetector(
onTap: () {
drawerManager.closeDrawer();
},
onHorizontalDragStart: (details) =>
drawerManager.setDragging(true),
onHorizontalDragUpdate: (details) {
if (!drawerManager.isDragging) return;
const minMove = 1;
if (details.delta.dx > minMove) {
drawerManager.openDrawer();
} else if (details.delta.dx < -minMove) {
drawerManager.closeDrawer();
}
drawerManager.setDragging(false);
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 250),
transform: Matrix4.translationValues(
drawerManager.xOffSet, drawerManager.yOffSet, 0)
..scale(drawerManager.scaleFactor),
child: AbsorbPointer(
absorbing: drawerManager.isDrawerOpen,
child: ClipRRect(
borderRadius: BorderRadius.circular(
drawerManager.isDrawerOpen ? 20 : 0),
child: Router(
routerDelegate: _appRouter,
backButtonDispatcher:
RootBackButtonDispatcher(),
),
),
)),
),
)
])));
},
),
);
}
}
Then the TodoScreen:
class _TodoRoosterScreenState extends State<TodoRoosterScreen> {
#override
Widget build(BuildContext context) => Consumer<DrawerManager>(
builder: (context, drawerManager, child) {
return Scaffold(
backgroundColor: LightColors.backgroundColor,
appBar: AppBar(
elevation: 0.0,
backgroundColor: LightColors.backgroundColor,
leading: DrawerMenuComponent(
onClick: () {
Provider.of<DrawerManager>(context, listen: false)
.openDrawer();
},
),
actions: <Widget>[
IconButton(
onPressed: () {},
icon: const Icon(Icons.search),
color: Colors.black38,
)
],
title: const Text(""),
),
body: _buildTodoList(context),
floatingActionButton: FloatingActionButton(
backgroundColor: LightColors.iconBlue,
onPressed: () async {
Provider.of<DrawerManager>(context, listen: false).goTo(AvailablePages.addTodo);
},
child: const Icon(Icons.add),
),
);
},
);
Widget _buildTodoList(BuildContext context) {
return StreamBuilder<List<Todo>>(
stream: Provider.of<ITodoRepository>(context, listen: false).watchTodos(),
builder: (context, AsyncSnapshot<List<Todo>> snapshot) {
if (snapshot.hasData) {
final todos = snapshot.data ?? [];
return ListView.builder(
itemCount: todos.length,
itemBuilder: (BuildContext context, int index) {
final todo = todos[index];
return Padding(
padding: const EdgeInsets.only(right: 16.0, left: 16.0),
child: SizedBox(
height: 70,
child: _buildSlidable(context, todo),
),
);
},
);
} else if(snapshot.hasError) {
return Text('${snapshot.error}');
}
return const Center(child: CircularProgressIndicator(),);
}
);
}
-----
And the add todo one:
class AddTodoScreen extends StatefulWidget {
const AddTodoScreen({Key? key}) : super(key: key);
static MaterialPage page() {
return MaterialPage(
name: HeyTaskPages.addTodoPath,
key: ValueKey(HeyTaskPages.addTodoPath),
child: const AddTodoScreen());
}
#override
_AddTodoScreenState createState() => _AddTodoScreenState();
}
class _AddTodoScreenState extends State<AddTodoScreen> with SingleTickerProviderStateMixin {
late AnimationController _controller;
#override
void initState() {
super.initState();
_controller =
AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
upperBound:1.3);
_controller.forward();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child){
return ClipPath(
clipper:AddTaskClipper(value: _controller.value),
child:child ,
);
},
child: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Text("Add Todo Screen"),
],
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: LightColors.iconBlue,
onPressed: () async {
//Provider.of<ITodoRepository>(context, listen: false).insertTodo(todo)
_controller.reverse().then((value) => Provider.of<DrawerManager>(context, listen: false).goTo(AvailablePages.todoRooster));
},
child: const Icon(Icons.done),
)
),
);
}
}
class AddTaskClipper extends CustomClipper<Path> {
final double value;
AddTaskClipper({required this.value});
#override
Path getClip(Size size) {
var path = Path();
path.addOval(Rect.fromCircle(center: Offset(size.width, size.height), radius: value *size.height));
path.close();
return path;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) => true;
}
The APPRouter
class AppRouter extends RouterDelegate
with ChangeNotifier, PopNavigatorRouterDelegateMixin {
#override
final GlobalKey<NavigatorState> navigatorKey;
final DrawerManager drawerManager;
AppRouter({
required this.drawerManager,
}) : navigatorKey = GlobalKey<NavigatorState>() {
drawerManager.addListener(notifyListeners);
}
#override
void dispose() {
drawerManager.addListener(notifyListeners);
super.dispose();
}
// 5
#override
Widget build(BuildContext context) {
return Navigator(
transitionDelegate: NoAnimationTransitionDelegate(),
key: navigatorKey,
onPopPage: _handlePopPage,
pages: [
if(drawerManager.selectedPage == AvailablePages.todoRooster) TodoRoosterScreen.page(openDrawer: drawerManager.openDrawer),
if(drawerManager.selectedPage == AvailablePages.categories) CategoriesScreen.page(openDrawer: drawerManager.openDrawer),
if(drawerManager.selectedPage == AvailablePages.settings) SettingsScreen.page(openDrawer: drawerManager.openDrawer),
if(drawerManager.selectedPage == AvailablePages.addTodo) AddTodoScreen.page(),
],
);
}
The drawer manager:
class DrawerManager extends ChangeNotifier{
AvailablePages selectedPage = AvailablePages.todoRooster;
void goTo(AvailablePages page){
if(selectedPage == page) {
closeDrawer();
return;
}
closeDrawer();
selectedPage = page;
}
double xOffSet = 0;
double yOffSet = 0;
double scaleFactor = 1;
bool isDrawerOpen = false;
bool isDragging = false;
void openDrawer() {
xOffSet = 230;
yOffSet = 100;
scaleFactor = 0.8;
isDrawerOpen = true;
notifyListeners();
}
void closeDrawer() {
xOffSet = 0;
yOffSet = 0;
scaleFactor = 1;
isDrawerOpen = false;
notifyListeners();
}
void setDragging(bool input) {
isDragging = input;
notifyListeners();
}
}
Could you give me a hand on How I could solve this or provide some snippets that could help? I tried many things and nothing I tried worked, and I know I am missing something.
Any help will be really appreciated, thank you all in advance!

Sort dynamically ListView in Flutter

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.