Flutter PageView not swipeable on web (using chrome web browser) - flutter

i want to create my portfolio using flutter web i use from page view to swipe between pages but page view not swiping this is my code i flow this guid but it not work for meFlutter PageView not swipeable on web (desktop mode)
class CustomPageView extends StatefulWidget {
const CustomPageView({Key? key}) : super(key: key);
#override
State<CustomPageView> createState() => _CustomPageViewState();
}
class _CustomPageViewState extends State<CustomPageView> {
var controller;
#override
void initState() {
controller = PageController(
initialPage: 0,
keepPage: true,
);
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: customAppBar(),
key: Globals.scaffoldKey,
endDrawer: const CustomEndDrawer(),
backgroundColor: whiteColor,
body: PageView(
controller: controller,
scrollDirection: Axis.vertical,
onPageChanged: (index) {
print(index);
},
// ignore: prefer_const_literals_to_create_immutables
children: [
const Home(),
Container(
color: Colors.red,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
)
]),
);
}
}

By default none of the scrollable widgets are scrolled with mouse on web.
You can override the behavior by wrapping your widget a configuration.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
class MouseDraggableScrollBehavior extends MaterialScrollBehavior {
#override
Set<PointerDeviceKind> get dragDevices => <PointerDeviceKind>{
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
};
}
After that wrap your PageView:
ScrollConfiguration(
behavior: MouseDraggableScrollBehavior(),
child: yourPageViewWidget,
)

Related

Flutter RefreshIndicator does not work when there's only one page

Flutter RefreshIndicator does not work when there's only one page. Did I use it the right way? Thanks!
If you change the pages with two or more pages, and drag down on the first page, the indicator works fine. But when there's only one page, it can't be dragged down.
Sample Codes:
void main() {
runApp(
MaterialApp(
title: 'Test',
home: Scaffold(
body: Bar(),
),
),
);
}
class Bar extends StatefulWidget {
#override
State<StatefulWidget> createState() => _BarState();
}
class _BarState extends State<Bar> {
List<Widget> pages;
final controller = PageController(initialPage: 0, keepPage: true);
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
pages = [_makePage('page 1')]; //, _makePage('page 2')];
return RefreshIndicator(
onRefresh: _onRefresh,
child: PageView(
controller: controller,
children: pages,
scrollDirection: Axis.vertical,
),
);
}
Future _onRefresh() async {
print('refresh!');
}
_makePage(String s) {
return Center(child: Text(s));
}
}
According to the Flutter docs, that is expected behavior for RefreshIndicator.
Please refer RefreshIndicator class
The RefreshIndicator will appear if its scrollable descendant can be overscrolled, i.e. if the scrollable's content is bigger than its viewport. To ensure that the RefreshIndicator will always appear, even if the scrollable's content fits within its viewport, set the scrollable's Scrollable.physics property to AlwaysScrollableScrollPhysics.
Please see the working code below :
import 'package:flutter/material.dart';
final Color darkBlue = const Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(
MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
title: 'Test',
home: Scaffold(
body: Bar(),
),
),
);
}
class Bar extends StatefulWidget {
#override
State<StatefulWidget> createState() => _BarState();
}
class _BarState extends State<Bar> {
List<Widget> pages;
final controller = PageController(initialPage: 0, keepPage: true);
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
pages = [_makePage('page 1')]; //, _makePage('page 2')];
return RefreshIndicator(
onRefresh: _onRefresh,
child: PageView(
physics: const AlwaysScrollableScrollPhysics(),
controller: controller,
children: pages,
scrollDirection: Axis.vertical,
),
);
}
Future _onRefresh() async {
print('refresh!');
}
_makePage(String s) {
return Center(child: Text(s));
}
}

Accessing Widget State instances in Flutter

I'm having trouble accessing instances of objects (or States) in Flutter, from other classes. I've tried a lot of fiddling using similar questions on the web, and am currently using 'GlobalKey', but I just can't get it working.
I'm trying to make a simple Flutter app where the State of a Widget gets accessed from another class, on button press:
import 'viewer.dart' as viewer;
(...)
onPressed: () {
//Works
print("Doing something");
//Doesn't work
viewer.key.currentState.nextPage();
},
My viewer.dart file looks contains a PageController, and a class containing that controller:
final key = new GlobalKey<_RegistryState>();
final PageController _controller = PageController(
initialPage: 0,
);
class Registry extends StatefulWidget {
Registry({ Key key }) : super(key: key);
#override
_RegistryState createState() => _RegistryState();
}
class _RegistryState extends State<Registry> {
void next() {
print("Doing something!");
_controller.nextPage();
}
#override
Widget build(BuildContext context) {
return PageView(
//physics: NeverScrollableScrollPhysics(), //Disable user manually scrolling
controller: _controller,
children: [
registry_screens.ScreenSplash(),
registry_screens.ScreenName(),
Text("Bye"),
],
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
}
The idea is that whenever the button gets pressed, the PageController moves to the next page (which is already there, I can scroll to it manually by swiping on the screen).
The app compiles fine, but when pressing the button I get the error 'NoSuchMethodError: invalid member on null: 'next''.
Am I using the correct approach for accessing instances of Widgets (or States)?
Flutter is a declarative framework. In this kind of environment, everytime that you want to change the view (or interface) you need to rebuild it. And if you rebuild what is holding the state, you would loose it. That's why it should not be responsible of holding the state of the program.
State management in Flutter is a broad subject with lots of options. As #DrSatan1 mentioned in the comments, in Flutter.dev you can find good documentation about state management using Provider, but you have lots of options with BLoC, ReduX, MobX, etc.
In your specific scenario, since it is simpler, you could accomplish that using a global object or Inherited Widget.
Global Object
globals.dart
currentPage=0;
In the Widget
import 'globals.dart' as global;
(...)
onPressed: () {
setState((){
globals.currentPage++;
});
},
viewer.dart
#override
Widget build(BuildContext context) {
return PageView(
//physics: NeverScrollableScrollPhysics(), //Disable user manually scrolling
currentPage: globals.currentPage, //instead of using PageController
children: [
registry_screens.ScreenSplash(),
registry_screens.ScreenName(),
Text("Bye"),
],
);
}
You could use the PageController as your global object. In that case you could pass the PageController down the widget tree. In this case, it would be better to use InheritedWidget instead.
InheritedWidget
As per docs, InheritedWidget is
Base class for widgets that efficiently propagate information down the
tree.
You can pass your PageController to all the widgets below the tree. Your viewer.dart would be:
(...)
#override
Widget build(BuildContext context) {
return MyInheritedWidget (
pageController: _controller,
child: PageView(
//physics: NeverScrollableScrollPhysics(), //Disable user manually scrolling
//controller: _controller, // Don't pass controller here
children: [
registry_screens.ScreenSplash(),
registry_screens.ScreenName(),
Text("Bye"),
],
);
);
}
(...)
// create the inherited widget wrapper. It could be done with [Builder][7] too, instead of a different Widget.
class MyInheritedWidget extends InheritedWidget {
final PageController pageController;
MyInheritedWidget({
Key key,
#required Widget child,
#required this.pageController,
}) : super(key: key, child: child);
#override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
}
(...)
After that you can access pageController in PageView or any Widget under it.
(...)
onPressed: () {
//Works
print("Doing something");
// Find closest InheritedWidget
MyInheritedWidget myInheritedWidget =
context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>()
// Get pageController from it
PageController controller = myInheritedWidget.pageController
// call nextPage()
nextPage();
},
(...)
Although both methods works in your specific scenario, you should check Flutter Docs about state management. Maybe you don't need the PageController at all.
It's generally a bad idea for state to be accessed externally. Instead, external classes should only interact with Widgets through the methods they expose.
I just made a video walking through the exact same onboarding setup you have using a PageView, which you can see here -- as I go through it step-by-step: https://www.youtube.com/watch?v=ji__FEKSnMw
In essence, it looks like this:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MainPage(),
debugShowCheckedModeBanner: false,
);
}
}
class MainPage extends StatefulWidget {
const MainPage({Key key}) : super(key: key);
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
PageController pageController = new PageController(initialPage: 0);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Container(
child: PageView(
controller: pageController,
physics: NeverScrollableScrollPhysics(),
children: [
Slide(
hero: Image.asset("./assets/hero-1.png"),
title: "Boost your traffic",
subtitle:
"Outreach to many social networks to improve your statistics",
onNext: nextPage),
Slide(
hero: Image.asset("./assets/hero-2.png"),
title: "Give the best solution",
subtitle:
"We will give best solution for your business isues",
onNext: nextPage),
Slide(
hero: Image.asset("./assets/hero-3.png"),
title: "Reach the target",
subtitle:
"With our help, it will be easier to achieve your goals",
onNext: nextPage),
Scaffold(
backgroundColor: Colors.white,
body: Center(
child: Text(
'Be kind to yourself',
style: kTitleStyle,
),
),
)
])),
),
);
}
void nextPage() {
pageController.nextPage(
duration: const Duration(milliseconds: 200), curve: Curves.ease);
}
}
class Slide extends StatelessWidget {
final Widget hero;
final String title;
final String subtitle;
final VoidCallback onNext;
const Slide({Key key, this.hero, this.title, this.subtitle, this.onNext})
: super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: hero),
Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Text(
title,
style: kTitleStyle,
),
SizedBox(
height: 20,
),
Text(
subtitle,
style: kSubtitleStyle,
textAlign: TextAlign.center,
),
SizedBox(
height: 35,
),
],
),
),
GestureDetector(
onTap: onNext,
child: Text(
"Skip",
style: kSubtitleStyle,
),
),
SizedBox(
height: 4,
)
],
),
);
}
}

How to: Stop ListView from rebuilding video_player widget?

I have managed to get a video to play on flutter-web, however, when I scroll the video in the ListView will rebuild/reload.
How can I stop the video_player (in the ListView) from being rebuilt when I scroll?
Sorry for the lengthy code sample.. I'm not sure how to condense it further
Any help would be great! Thanks
import 'package:flutter/material.dart';
import '../../widgets/video/chewie_video.dart';
import 'package:video_player/video_player.dart';
class TestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
Container(height: 1000),
Center(
child: ChewieVideo(
videoPlayerController:
VideoPlayerController.asset('assets/video.mp4'),
),
),
Container(height: 1000),
],
),
);
}
}
import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class ChewieVideo extends StatefulWidget {
final VideoPlayerController videoPlayerController;
final bool looping;
ChewieVideo({
#required this.videoPlayerController,
this.looping,
Key key,
}) : super(key: key);
#override
_ChewieVideoState createState() => _ChewieVideoState();
}
class _ChewieVideoState extends State<ChewieVideo> {
ChewieController _chewieController;
#override
void initState() {
super.initState();
_chewieController = ChewieController(
videoPlayerController: widget.videoPlayerController,
autoInitialize: true,
aspectRatio: 16 / 9,
looping: widget.looping,
errorBuilder: (context, errorMessage) {
return Center(
child: Text(
errorMessage,
style: TextStyle(color: Colors.white),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Chewie(
controller: _chewieController,
),
);
}
#override
void dispose() {
super.dispose();
widget.videoPlayerController.dispose();
_chewieController.dispose();
}
}
I'm not sure if this is a good or just temporary fix but this is how I have managed to get it to work...
If you are using inside of a ListView/CustomScrollView, then you need to adjust the cacheExtent for either of these widgets... I have set mine to 1000.
I'm not quite sure what's happening but it works...
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: primaryBlack,
drawer: NavDrawer(),
body: Scrollbar(
child: CustomScrollView(
cacheExtent: 1000,
slivers: <Widget>[
...
after doing that chewie and video_player plugins shouldn't rebuild/refresh inside of a ListView

flutter PageView inside TabBarView: scrolling to next tab at the end of page

I have 3 tabs and each tab has a PageView inside.
At the end of the PageView, I want to be able to scroll to the next tab.
Is there a way I can do TabBar scroll instead of PageView scroll if there's no more page to the direction? (only left or right scroll)
Here's the sample code.
When I scroll to right at the last page of the 1st tab, I want to see the first page of the 2nd tab.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: ScrollableTabsDemo());
}
}
class _Page {
const _Page({ this.icon, this.text });
final IconData icon;
final String text;
}
const List<_Page> _allPages = <_Page>[
_Page(icon: Icons.grade, text: 'TRIUMPH'),
_Page(icon: Icons.playlist_add, text: 'NOTE'),
_Page(icon: Icons.check_circle, text: 'SUCCESS'),
];
class ScrollableTabsDemo extends StatefulWidget {
static const String routeName = '/material/scrollable-tabs';
#override
ScrollableTabsDemoState createState() => ScrollableTabsDemoState();
}
class ScrollableTabsDemoState extends State<ScrollableTabsDemo> with SingleTickerProviderStateMixin {
TabController _controller;
#override
void initState() {
super.initState();
_controller = TabController(vsync: this, length: _allPages.length);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final Color iconColor = Theme.of(context).accentColor;
return Scaffold(
appBar: AppBar(
title: const Text('Scrollable tabs'),
bottom: TabBar(
controller: _controller,
isScrollable: true,
tabs: _allPages.map<Tab>((_Page page) {
return Tab(text: page.text, icon: Icon(page.icon));
}).toList(),
),
),
body: TabBarView(
controller: _controller,
children: _allPages.map<Widget>((_Page page) {
return SafeArea(
top: false,
bottom: false,
child: PageView.builder(
itemBuilder: (context, position)
{
return Container(child: Center(child: Text(position.toString())));
},
itemCount: 5,
),
);
}).toList(),
),
);
}
}
Add to the pageBuilder the onPageChange param. Then check if its the last page, if so, animate the tabController to the nextPage.
onPageChanged: (page) {
if (page == _allPages.length &&
(_controller.index + 1) < _controller.length) {
_controller.animateTo(_controller.index + 1);
}
},
itemCount: _allPages.length + 1,

Flutter: preserve state when changing body in Drawer

I am creating a Flutter application with a navigation drawer by using the Drawer class of the Material library. The Widget containing the Drawer is a StatefulWidget and the Scaffold's content is displayed according to the selected item on the navigation drawer. The content is either WidgetOne or WidgetTwo, both maintaining their own state as StatefulWidgets. See the code example below.
At the moment, when I change from one widget to another and back, the whole state of the earlier displayed widget is reloaded. This is not ideal, since both widgets have network calls from an API, and need to be redrawn accordingly.
What I've tried so far
Implementing AutomaticKeepAliveClientMixin on both sub widgets, as suggested here: https://stackoverflow.com/a/50074067/4009506. However, this does not seem to work.
Using an IndexedStack as suggested here: https://stackoverflow.com/a/54999503/4009506. This loads all widgets directly, even if they are not yet displayed.
Code
class DrawerWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => _DrawerState();
}
class _DrawerState extends State<DrawerWidget> {
Widget _activeWidget;
#override
void initState() {
_activeWidget = FirstWidget();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Drawer demo")),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
ListTile(
title: Text("First Widget"),
onTap: () {
setState(() {
_activeWidget = FirstWidget();
});
},
),
ListTile(
title: Text("Second Widget"),
onTap: () {
setState(() {
_activeWidget = SecondWidget();
});
},
),
],
),
),
body: _activeWidget);
}
}
class FirstWidget extends StatefulWidget {
// [..]
}
class SecondWidget extends StatefulWidget {
// [..]
}
Desired result
WidgetOne and WidgetTwo are only loaded on initial load (after selecting them in the Drawer). Switching to another widget and back should not reload the widget if it was already loaded earlier. The sub widgets should not load all directly, only when they are initially pressed.
Actual result
Both FirstWidget and SecondWidget are reloaded and redrawn each time they are selected in the Drawer.
I resolved this issue by using a PageView and implementing AutomaticKeepAliveClientMixin on all sub widgets:
class DrawerWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => _DrawerState();
}
class _DrawerState extends State<DrawerWidget> {
final _pageController = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Drawer demo")),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
ListTile(
title: Text("First Widget"),
onTap: () {
_pageController.jumpToPage(0);
},
),
ListTile(
title: Text("Second Widget"),
onTap: () {
_pageController.jumpToPage(1);
},
),
],
),
),
body: PageView(
controller: _pageController,
children: <Widget>[
FirstWidget(),
SecondWidget()
],
physics: NeverScrollableScrollPhysics()
));
}
}
class FirstWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => _FirstWidgetState();
}
class _FirstWidgetState extends State<FirstWidget> with AutomaticKeepAliveClientMixin<FirstWidget> {
// [..]
#override
bool get wantKeepAlive => true;
}
class SecondWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => _SecondWidgetState();
}
class _SecondWidgetState extends State<SecondWidget> with AutomaticKeepAliveClientMixin<SecondWidget> {
// [..]
#override
bool get wantKeepAlive => true;
}
Now, all widgets are only loaded upon initial switch in the navigation drawer and do not get reloaded when switching back.