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
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 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
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.
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!
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.