How do I set the initial page of PageView in Flutter? - flutter

I have a PageView with four pages in it. I want to start on the third page. That means there are two pages available when the user scrolls up and one page when the user scrolls down.
I tried:
home: PageView(
controller: MyPageController(),
children: [Page1(), Page2(), Page3(), Page4()],
scrollDirection: Axis.vertical,
),
With:
class MyPageController extends PageController {
#override
int get initialPage => 3;
}
Unfortunately, that doesn't help me.

PageController constructor has named parameter initialPage. You can use it, just create the controller somewhere outside of build function:
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
PageController controller;
#override
void initState() {
super.initState();
controller = PageController(initialPage: 3);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: PageView(
controller: controller,
children: [
for (var i = 0; i < 4; i++)
Text('test $i')
],
scrollDirection: Axis.vertical,
)
)
),
);
}
}

For me initialization of PageController with initialPage didn't work for a large number of pages. I also noticed that there is scrolling animation which is undesirable if you want to land to desired page directly.
I used following
PageController _pageViewController = PageController();
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_pageViewController.hasClients)
_pageViewController.jumpToPage(3);
});
super.didChangeDependencies();
}

You need to set initial page like,
PageController _controller = PageController(initialPage: 3);

Related

How to calculate page stay duration in Flutter?

I want to calculate every pages stay duration of my flutter app. The stay duration means from page becomes visible to page hide.
How do i do this?
Any idea might help me.
You can use this wrapper widget,
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: PageView.builder(
itemCount: 4,
itemBuilder: (context, index) => TrackWidgetLifeTime(
child: Text("$index"),
lifeTime: (spent) {
log("I've spend ${spent.toString()}");
},
),
),
),
);
}
}
class TrackWidgetLifeTime extends StatefulWidget {
const TrackWidgetLifeTime({super.key, this.lifeTime, required this.child});
final Function(Duration spent)? lifeTime;
final Widget child;
#override
State<TrackWidgetLifeTime> createState() => _TrackWidgetLifeTimeState();
}
class _TrackWidgetLifeTimeState extends State<TrackWidgetLifeTime> {
var stopwatch = Stopwatch();
#override
void initState() {
super.initState();
stopwatch.start();
}
#override
void dispose() {
if (widget.lifeTime != null) widget.lifeTime!(stopwatch.elapsed);
super.dispose();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
An idea would be to capture the time at initState for stateful widgets, or under build method for stateless widgets, and compare it with the time on leave, as follows:
late DateTime started;
DateTime? ended;
#override
void initState() {
started = DateTime.now();
super.initState();
}
void onLeave() {
ended = DateTime.now();
if (ended != null) {
var lapse = Duration(microseconds: ended!.microsecondsSinceEpoch-started.microsecondsSinceEpoch);
print("Viewed page ${lapse.inSeconds} seconds");
}
}
To learn how to detect when the user moves away from the current page take a look at this answer:
Detect if the user leaves the current page in Flutter?

Best practice for calling an API or method at start point of Flutter App that needs context?

What is the best approach to calling an API or method when a screen wants to start in the Flutter app?
As I know in initState method, there is no context so I use didChangeDependencies method like below, but I always think there is a better way to tackle this issue. Any idea, snippet, or sample project link will be appreciated.
class HomeTabScreen extends StatefulWidget {
static String route = 'accountTab';
#override
_HomeTabScreenState createState() => _HomeTabScreenState();
}
class _HomeTabScreenState extends State<HomeTabScreen> {
late HomeTabViewModel homeTabVM;
var firstTime = true;
late TabController _controller;
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (firstTime) {
homeTabVM = context.watch<HomeTabViewModel>();
homeTabVM.getUserData(context);
_controller = TabController(
length: homeTabVM.list.length,
vsync: this,
initialIndex: 1,
);
firstTime = false;
}
homeTabVM.listKey = GlobalKey();
homeTabVM.listItems.clear();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.transparent,
child: Center(
child: CircularProgressIndicator(),
),
));
}
}

Flutter PageView keep rebuilding the main widget on setState value

currently flutter app structure
StackedHome has a pageview with 2 children
Pageview(parent):
HomeScreen(child#1)
Vertical PageView
bottom navigation bar
UserProfilePage(child#2)
HomeScreen should pass the index value to UserProfilePage, so when scrolling horizontally, we will get user profilescreen with id passed to that received from HomeScreen. based on the id passed i will display related user profile
Here is sample video showing the problem :
https://drive.google.com/file/d/1tIypNOHewcFSo2Pf-F97hsQGfDgNVqfW/view?usp=sharing
Problem:
i managed to do that and its working fine, but my problem on setState of that variable
setState(() {
_postIndex = postIndex;
});
on each HomeScreen > onPageChanged call i am updating the index value pass it to the parent (StackedHome) class, and since there is a setState to update profile index (UserProfilePage)...the whole app will be rebuild on each pageview change...
What i need is to disable that main widget to be rebuilt again and again on value update..
StackedHome
class StackedHome extends StatefulWidget {
final int data;
final Function(int) onDataChange;
const StackedHome({
this.data,
this.onDataChange,
Key key,
}) : super(key: key);
#override
_StackedHomeState createState() => _StackedHomeState();
}
class _StackedHomeState extends State<StackedHome>{
PageController pageController;
int _count = 0;
int _postIndex = 0;
void _postId(int postIndex) {
//This cuasing main screen to be rebuilt everytime on pageview scroll
//but getting the value correctly
setState(() {
_postIndex = postIndex;
});
}
#override
void initState() {
super.initState();
pageController = PageController();
}
#override
void dispose() {
pageController.dispose();
super.dispose();
}
int index = 0;
#override
Future<void> _refreshPosts() async {
PostApi postApi = PostApi();
setState(() {
postApi.fetchAllPosts();
});
}
Widget build(BuildContext context) {
PostApi postApi = PostApi();
return FutureBuilder(
future: postApi.fetchAllPosts(),
builder: (BuildContext context, AsyncSnapshot<List<Post>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return apiError('No Connection Made');
break;
case ConnectionState.waiting:
case ConnectionState.active:
return ApiLoading(color:0xff000000);
break;
case ConnectionState.done:
if (snapshot.hasError) {
return apiError(snapshot.error.toString());
}
if (snapshot.hasData) {
return _drawPostsList(snapshot.data, context);
}
break;
}
return Container();
},
);
}
Widget _drawPostsList(List<Post> posts, BuildContext context) {
return PageView(
reverse: true,
children: <Widget>[
HomeScreen(
posts: posts,
index: index,
postId: _postId,//function Passed
),
UserProfilePage(
posts: posts,
index: _postIndex,
)
],
);
}
}
HomeScreen
class HomeScreen extends StatefulWidget {
#override
final List posts;
final int index;
final Function(int) postId;
int getPage() {
return value;
}
void setPage(int page) {
value = page;
}
HomeScreen({Key key, this.posts, this.index, this.postId}) : super(key: key);
HomeScreenState createState() => HomeScreenState();
}
class HomeScreenState extends State<HomeScreen>
with SingleTickerProviderStateMixin {
final PageController _controller = PageController();
PageController _pageController = PageController();
int index = 0;
#override
void initState() {
super.initState();
//Set pageview inital page
_pageController = PageController(
keepPage: true,
initialPage: widget.getPage(),
);
}
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _refreshPosts,
child: Stack(children: <Widget>[
PageView.builder(
controller: _pageController,
onPageChanged: (index) => setState(() {
.
widget.postId(index);//I am calling parent class and updating the vlaue with new index value
.
}),
scrollDirection: Axis.vertical,
itemBuilder: (context, position) {
//Build image lists
return _homeList(widget.posts, position);
},
),
BottomNavigation("light"),
]),
);
}
}
i hope my problem is clear enough....i need to pass the value to parent so i can pass it to second child which is the profile screen so it will show user profile realted to that post
Ohh wow, managed to solve this problem using provider and consumer, by listening to any update on index id... this post helped me to solve it https://medium.com/flutter-nyc/a-closer-look-at-the-provider-package-993922d3a5a5

How to call a function from another pageview class to another in Flutter?

I need help calling a method of another class in a pageview. I have an app which has a pageview consisting of two classes, Page 1 and Page 2 respectively. Both of them are stateful widgets.
I am trying to call a method from Page2 from Page1, but it is difficult for me as both of them do not have a child/parent relationship so I could not use a callback function. In fact, both of them are a child of another parent class (MainParent). I tried using globalkey but i received currentState is null error.
Your help is greatly appreciated. Thanks!
class MainParent extends StatefulWidget{
#override
MainParentState createState() => MainParentSate();
}
class MainParentState extends State<MainParent> {
PageController pageController;
#override
initState(){
pageController = new PageController(
}
List<Widget> _showPageList(AppModel appModel){
List<Widget> pageList = new List();
pageList.add(Page1(/*some named parameters pass here*/));
pageList.add(Page2(/*some named parameters pass here*/));
return pageList;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
physics: new NeverScrollableScrollPhysics(),
controller: pageController,
onPageChanged: _onPageChanged,
children: _showPageList(),
)
);
}
}
Page 1 class
class Page1 extends StatefulWidget{
#override
Page1State createState() => Page1State();
}
class Page1State extends State<Page1> {
_callPage2Function(){
//animate to page 2 and mount it
MainParent.of(context).pageController.animateToPage(
1,
duration: const Duration(milliseconds: 700),
curve: Curves.fastLinearToSlowEaseIn);
//this doesn't work
Page2State page2state = new Page2State();
page2state.refreshList();
//creating a global key doesnt work as well, gets currentstate is null
GlobalKey<Page2State> page2Key = new GlobalKey<Page2State>();
page2Key.currentState.refreshList();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: GestureDetector(
onTap: () => _callPage2Function()
)
)
);
}
}
Page 2 class
class Page2 extends StatefulWidget{
#override
Page2State createState() => Page2State();
}
class Page2State extends State<Page2> {
refreshList(){
//this function refreshes the list
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: ListView.builder(
//some child widgets here
)
)
);
}
}
Edit: Edited my code. Turns out I have to navigate to page2 tab first, because when I am in page 1, page 2 is not mounted to the screen. So i added a pageController and navigate to page2 first.
The error in your code is that you are not injecting the key in you Page2 instance when using it, thats why the currentState in null.
Usually when i want to achieve this, i use the key attribute like so
class Page2 extends StatefulWidget {
static final GlobalKey<Page2State> staticGlobalKey =
new GlobalKey<Page2State>();
Page2() : super(key: Page2.currentStateKey); // Key injection
#override
Page2State createState() => Page2State();
}
class Page2State extends State<Page2> {
refreshList() {
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: ListView.builder(
//some child widgets here
)));
}
}
then to call the refreshList() from anywhere in your app just do
Page2.staticGlobalKey.currentState.refreshList();
You could work around and use another approach instead of static key, but this should be a working solution.
In PageView, Only one page is mounted(or active) at a time.
Here when you call page2Key.currentState.refreshList(); from Page1, the Page2 is currently unmounted(or deactive). So the state itself is null.
So you cannot do setState of another page.

Change selected tab in TabView given state change in ChangeNotifier

I have the following class for holding my state:
import 'package:flutter/foundation.dart';
enum ActiveProduct {
HOME,
BURGUNDY,
VIRTUAL
}
class ActiveProductModel extends ChangeNotifier {
ActiveProduct _activeProduct = ActiveProduct.HOME;
ActiveProduct get value => _activeProduct;
void set(ActiveProduct newValue) {
_activeProduct = newValue;
notifyListeners();
}
}
Whenever the "ActiveProduct" changes, I want to change the selected tab in a TabView.
Currently I have setup the application like this:
class MainScaffold extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
body: TabBarView(
children: [
Text("hello! abc"),
Text("hello! sdsadsa"),
Text("hello! 231321"),
],
),
),
);
}
}
How can I change the selected tab in the TabBarView when the ActiveProduct changes?
I have tried wrapping the MainScaffold in a Consumer and set the initialIndex value of DefaultTabController. This didn't work however.
I am using the Provider package for state management.
What I would do is transforming your MainScaffold into a StatelessWidget and then, I would not use a DefaultTabController but instead I would use a TabController and pass it as an argument to the TabBarView.
Then in the initState of the MainScaffoldState, I would listen on the ActiveProductModel and use tabController.animateTo(yourpage) whenever something is fired.
I'm not sure that I was very clear in my explanation so here is an example:
class MainScaffold extends StatefulWidget {
#override
_MainScaffoldState createState() => _MainScaffoldState();
}
class _MainScaffoldState extends State<MainScaffold> with SingleTickerProviderStateMixin {
TabController tabController;
/// Just for the example
ActiveProductModel activeProductModel = ActiveProductModel();
#override
void initState() {
// Initiate the tabController
tabController = TabController(vsync: this, length: 3);
activeProductModel.addListener(() {
if (mounted)
/// Here you can do whatever you want, just going to page 2 for the example.
tabController.animateTo(2);
});
super.initState();
}
#override
void dispose() {
tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: TabBarView(
controller: tabController,
children: <Widget>[
Text("hello! abc"),
Text("hello! sdsadsa"),
Text("hello! 231321"),
],
),
);
}
}