Flutter: How to switch from one page view to another? - flutter

I am creating a flutter app where I have a screen which contains a PageView Builder.There are two other screens which are shown depending on the condition in the PageView Builder(i.e. QuizResult and QuizQuestion).
If the index if equal to the number of quiz questions it will show the Quiz Result Page otherwise it will continue showing the next question on the QuizQuestion Page(Have hardcoded the question for this example).
I want to know what logic can I add in the FlatButton widget onPressed in the QuizResult Page so that I can reset the index of the PageView back to the start and can again show the QuizQuestion Page again?
This is the QuizScreen
class QuizScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView.builder(
physics: const NeverScrollableScrollPhysics(),
controller: state.controller,
scrollDirection: Axis.vertical,
itemBuilder: (BuildContext context, int index) {
if (index == quiz.questions.length) {
return QuizResult();
} else {
return QuizQuestion(question: quiz.questions[index]);
}
},
)
);
}
}
The QuizQuestion Widget looks like this
class QuizQuestion extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(body: Text('Which is the fastest animal?'));
}
}
The QuizResult Widget looks like this
class QuizResult extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(body: FlatButton(onPressed(){ < Logic Here ?? >}, child:Text('PlayAgain')));
}
}

I have used your code to reproduce the issue and then modified it for readability.
All you need is to assign an itemCount. And it needs to be the length of the quiz plus one. We need to add one more page to show the result page.
itemCount: questions.length + 1,
As you already predicted, we need to check the index of the shown page and create a condition based on it.
itemBuilder: (context, index) {
if (index == questions.length) {
return QuizResult(onPressed: onPressed);
}
return QuizQuestion(question: questions[index]);
},
Lastly, you need to jump to the first page using the controller to reset the quiz. The easiest way to do that is: Passing the onPressed function via constructor to QuizResult:
void onPressed() {
controller.jumpToPage(0);
currentPage = 0;
}
Please check the following solution:
main.dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) => MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const QuizScreen(),
);
}
class QuizScreen extends StatefulWidget {
const QuizScreen({Key? key}) : super(key: key);
#override
State<QuizScreen> createState() => _QuizScreenState();
}
class _QuizScreenState extends State<QuizScreen> {
final PageController controller = PageController();
int currentPage = 0;
List<String> questions = [
'Which is the fastest animal?',
'Which is the slowest animal?',
'Which is the longest animal?',
];
#override
Widget build(BuildContext context) => Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: PageView.builder(
physics: const NeverScrollableScrollPhysics(),
controller: controller,
scrollDirection: Axis.vertical,
itemCount: questions.length + 1,
itemBuilder: (context, index) {
if (index == questions.length) {
return QuizResult(onPressed: onPressed);
}
return QuizQuestion(question: questions[index]);
},
),
),
TextButton(
onPressed: () {
if (currentPage == questions.length) return;
controller.jumpToPage(++currentPage);
},
child: const Text('Next'),
)
],
),
),
);
void onPressed() {
controller.jumpToPage(0);
currentPage = 0;
}
}
class QuizQuestion extends StatelessWidget {
const QuizQuestion({Key? key, required this.question}) : super(key: key);
final String question;
#override
Widget build(BuildContext context) => Center(
child: Text(question),
);
}
class QuizResult extends StatelessWidget {
const QuizResult({Key? key, required this.onPressed}) : super(key: key);
final void Function() onPressed;
#override
Widget build(BuildContext context) => Scaffold(
body: Center(
child: TextButton(
onPressed: onPressed,
child: const Text('Play Again'),
),
),
);
}
You can access the modified source code through GitHub.
If you have further questions, please don't hesitate to write in the comments.

Related

Icon change provider index

Hi I want to change the icon when pressed button.
So I used provider method.
Icon Widget
class LockeryIcon extends StatelessWidget {
final String text;
LockeryIcon({required this.text});
#override
Widget build(BuildContext context) {
return IconButton(
onPressed: () {
Navigator.of(context).pushNamed(SecondView);
print(text);
},
icon: Icon(context.watch<ProviderA>().isIcon),
);
}
}
Listview builder
class Abc extends StatelessWidget {
final List _lock1 = [
'abc 1',
'abc 2',
'abc 3',
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
shrinkWrap: true,
itemCount: _lock1.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(30.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
LockeryIcon(text: _lock1[index]),
],
),
);
},
),
);
}
}
Main Screen
class MainView extends StatelessWidget {
const MainView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Abc(),
);
}
Second View
class SecondView extends StatelessWidget {
const SecondView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: TextButton(
onPressed: () {
context.read<LockeryProvider>().change();
Navigator.of(context).pop();
},
child: Text('Done'),
),
);
}
}
Provider
class ProviderA with ChangeNotifier {
IconData _isIcon = Icons.access_time_filled_sharp;
IconData get isIcon => _isIcon;
void change() {
_isIcon = Icons.add_location;
notifyListeners();
}
}
My problem is when I clicked this all the icons are being changed.
Is there a way to pass index to only change the relevant button???
Or my method is not correct?
Please help me on this
Thank you
You have to store an integer in your provider:
class ProviderA with ChangeNotifier {
int _index = -1;
int get isIndex => _index;
void change(int index) {
_index = index;
notifyListeners();
}
}
and check the index in your Icon class like this:
class LockeryIcon extends StatelessWidget {
final String text;
final int listViewIndex;
LockeryIcon({required this.text,required this.listViewIndex});
#override
Widget build(BuildContext context) {
return IconButton(
onPressed: () {
Navigator.of(context).pushNamed(SecondView);
print(text);
},
icon: Icon(context.watch<ProviderA>().isIndex == listViewIndex ? firstIcon:secondIcon),
);
}
}
finally, use the change function in which you press your button.

Force rebuild of a stateful child widget in flutter

Let's suppose that I have a Main screen (stateful widget) where there is a variable count as state. In this Main screen there is a button and another stateful widget (let's call this MyListWidget. MyListWidget initialize it's own widgets in the initState depending by the value of the count variable. Obviously if you change the value of count and call SetState, nothing will happen in MyListWidget because it create the values in the initState. How can I force the rebuilding of MyListWidget?
I know that in this example we can just move what we do in the initState in the build method. But in my real problem I can't move what I do in the initState in the build method.
Here's the complete code example:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int count = 5;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
Expanded(
child: MaterialButton(
child: Text('Click me'),
color: Colors.red,
onPressed: () {
setState(() {
count++;
});
},
),
),
MyListWidget(count),
],
));
}
}
class MyListWidget extends StatefulWidget {
final int count;
const MyListWidget(this.count, {Key? key}) : super(key: key);
#override
_MyListWidgetState createState() => _MyListWidgetState();
}
class _MyListWidgetState extends State<MyListWidget> {
late List<int> displayList;
#override
void initState() {
super.initState();
displayList = List.generate(widget.count, (int index) => index);
}
#override
Widget build(BuildContext context) {
return Expanded(
child: ListView.builder(
itemBuilder: (BuildContext context, int index) => ListTile(
title: Text(displayList[index].toString()),
),
itemCount: displayList.length,
),
);
}
}
I don't think the accepted answer is accurate, Flutter will retain the state of MyListWidget because it is of the same type and in the same position in the widget tree as before.
Instead, force a widget rebuild by changing its key:
class _MyHomePageState extends State<MyHomePage> {
int count = 5;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
Expanded(
child: MaterialButton(
child: Text('Click me'),
color: Colors.red,
onPressed: () {
setState(() {
count++;
});
},
),
),
MyListWidget(count, key: ValueKey(count)),
],
),
);
}
}
Using a ValueKey in this example means the state will only be recreated if count is actually different.
Alternatively, you can listen to widget changes in State.didUpdateWidget, where you can compare the current this.widget with the passed in oldWidget and update the state if necessary.
USE THIS:
class _MyHomePageState extends State<MyHomePage> {
int count = 5;
MyListWidget myListWidget = MyListWidget(5);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
Expanded(
child: MaterialButton(
child: Text('Click me'),
color: Colors.red,
onPressed: () {
setState(() {
count++;
myListWidget = MyListWidget(count);
});
},
),
),
myListWidget,
],
));
}
}

How to setState widget by other widget Flutter ,simplecode below

right widget has gesterdetector that adds a String ("ZzZ") to List;
left widget shows all String there in String list by List view Buildder,
right widget adds "ZzZ" to list after pressing the button successfully but it dosent sets ui state...
in android studio after hot reload it shows all added "ZzZ"
import 'package:flutter/material.dart';
List<String> ListOfZzZ=[];
class homescreen extends StatefulWidget {
#override
_homescreenState createState() => _homescreenState();
}
class _homescreenState extends State<homescreen> {
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: Row(children: [
Expanded(child:RightSidewidget()),
Expanded(child:LeftSidewidget())
],
)),
);
}
}
class RightSidewidget extends StatefulWidget {
#override
_RightSidewidgetState createState() => _RightSidewidgetState();
}
class _RightSidewidgetState extends State<RightSidewidget> {
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Container(child:Text("add new ZzZ"),),
**onTap: (){
setState(() {
ListOfZzZ.add("ZzZ");
});},);**
}
}
class LeftSidewidget extends StatefulWidget {
#override
_LeftSidewidgetState createState() => _LeftSidewidgetState();
}
class _LeftSidewidgetState extends State<LeftSidewidget> {
#override
Widget build(BuildContext context) {
return Container(child:
ListView.builder(
itemCount: ListOfZzZ.length,
itemBuilder: (context,index)=>Text(ListOfZzZ[index])),);
}
}
check the Provider package it can help you achieve what you want, ere is a really good tutorial by the flutter devs showing how to use manage the state of your app and notify widgets of the changes other widgets have.
setState rebuild in very specyfic way. you can read about this in here:
https://api.flutter.dev/flutter/widgets/State/setState.html
in simple world setState call the nearest build (I think this is not full true, but this intuitions works for me)
In your code when you tap right widget and call setState only rightwidget will be rebuild.
So this is the easy solutions:
Make left and right widget statless.
In homescreen in row add gestureDetector(or textButton like in my example) and here call setState. When you do that, all homeSreen will be rebuild so left and right widget too. and your list will be actual. Here is example:
List<String> ListOfZzZ = [];
class homescreen extends StatefulWidget {
#override
_homescreenState createState() => _homescreenState();
}
class _homescreenState extends State<homescreen> {
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: Row(
children: [
Expanded(
child: TextButton(
onPressed: () => setState(() {
ListOfZzZ.add("ZzZ");
}),
child: RightSidewidget())),
Expanded(child: LeftSideWidget())
],
)),
);
}
}
class RightSidewidget extends StatelessWidget {
const RightSidewidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.amber[50],
child: Text("add new ZzZ"),
);
}
}
class LeftSideWidget extends StatelessWidget {
const LeftSideWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: ListView.builder(
itemCount: ListOfZzZ.length,
itemBuilder: (context, index) => Text(ListOfZzZ[index])),
);
}
}
The hard way, but more elegant and better is to use some state manager like bloc. Here is official site: https://bloclibrary.dev/#/gettingstarted
there is a lot of tutorials and explanations. But this is not solutions for 5 minutes.
Edit: I make some solution with BLoC. I hope this help. I use flutter_bloc and equatable packages in version 7.0.1
void main() {
EquatableConfig.stringify = kDebugMode;
Bloc.observer = SimpleBlocObserver();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('myList'),
),
body: BlocProvider(
create: (context) => MylistBloc()..add(AddToList('Start')),
child: Row(
children: [
Expanded(flex: 1, child: buttonsPanel()),
Expanded(flex: 1, child: ListOfZzZ()),
],
),
),
),
);
}
}
class ListOfZzZ extends StatefulWidget {
const ListOfZzZ({Key? key}) : super(key: key);
#override
_ListOfZzZState createState() => _ListOfZzZState();
}
class _ListOfZzZState extends State<ListOfZzZ> {
late MylistBloc _mylistBloc;
#override
Widget build(BuildContext context) {
return BlocBuilder<MylistBloc, MylistState>(
//builder: (context, state) {return ListView.builder(itemBuilder: (BuildContext context,int index){return ListTile(title: state.positions[index];)},);},
builder: (context, state) {
if (state.positions.isEmpty) {
return const Center(child: Text('no posts'));
} else {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text(state.positions[index]));
},
itemCount: state.positions.length,
);
}
},
);
}
}
class buttonsPanel extends StatefulWidget {
const buttonsPanel({Key? key}) : super(key: key);
#override
_buttonsPanelState createState() => _buttonsPanelState();
}
class _buttonsPanelState extends State<buttonsPanel> {
late MylistBloc _mylistBloc;
#override
void initState() {
super.initState();
_mylistBloc = context.read<MylistBloc>();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () => {_mylistBloc.add(AddToList('Spam'))},
child: Text('Spam')),
TextButton(
onPressed: () => {_mylistBloc.add(AddToList('Ham'))},
child: Text('Ham')),
],
);
}
class SimpleBlocObserver extends BlocObserver {
#override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print(transition);
}
#override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
print(error);
super.onError(bloc, error, stackTrace);
}
}
class MylistState extends Equatable {
final List<String> positions;
final int lenght;
const MylistState({this.positions = const <String>[], this.lenght = 0});
#override
List<Object> get props => [positions];
#override
String toString() => 'Lenght: {$lenght} Positions: {$positions}';
#override
MylistState copyWith(List<String>? positions) {
return MylistState(positions: positions ?? this.positions);
}
}
abstract class MylistEvent extends Equatable {
const MylistEvent();
#override
List<Object> get props => [];
}
class AddToList extends MylistEvent {
final String posToAdd;
#override
AddToList(this.posToAdd);
}
class MylistBloc extends Bloc<MylistEvent, MylistState> {
MylistBloc() : super(MylistState(positions: const <String>[]));
#override
Stream<MylistState> mapEventToState(
MylistEvent event,
) async* {
if (event is AddToList) {
yield await _mapListToState(state, event.posToAdd);
}
}
Future<MylistState> _mapListToState(
MylistState state, String posToAdd) async {
List<String> positions = [];
positions.addAll(state.positions);
positions.add(posToAdd);
return MylistState(positions: positions, lenght: positions.length);
}
}
}

How to change the page using Navigator.push so that the body and header animation is different, while the footer remains unchanged

I am trying to go to the next page using Navigator.push and at the same time change only the body on the page. I only got this when, for example, I wrap the index of page 2 in materialApp. But when I decided to make the animation (it smoothly pushes the old page to the left and pushes the new page to the right), it turned out that she pushed the old page, but behind it was exactly the same motionless page, which was later blocked by the new one.
I understood this in such a way that the first deleted page was an index 2 page, which is wrapped in MaterialApp, and behind it is exactly the same fixed MaterialApp for the entire application. At the moment, I have no idea how to remove a fixed page. I gave a picture of how I am currently navigating in the application, it may not be perfect, but I do not know better, any help would be appreciated.
In many applications, I see such an animation that the header fades out smoothly and at the same time a new one appears. And the body at this moment is replaced with the old page with a smooth movement, I really like it and I want to do the same.
You can try to use nested Navigator inside your scaffold.
Page index 1,2 and 3 will be inside the root Navigator under material app. Page 2 will contain another Navigator to fit your purpose.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final GlobalKey<NavigatorState> outgoingKey = GlobalKey<NavigatorState>();
return MaterialApp(
title: 'Sample',
home: Scaffold(
body: PageView(
children: <Widget>[
Page1(),
Page2(navigatorKey: outgoingKey,),
Page3(),
],
pageSnapping: false,
scrollDirection: Axis.horizontal,
physics: BouncingScrollPhysics(),
),
bottomNavigationBar: /*SomeBottomNavigationBar()*/,
),
);
}
}
class Page2 extends StatelessWidget {
Page2({Key key, this.navigatorKey}) : super(key: key);
final GlobalKey<NavigatorState> navigatorKey;
#override
Widget build(BuildContext context) {
return Container(
child: Column(children: [
Expanded(
child: Navigator(
key: navigatorKey, // you need to use this to pop i.e. navigatorKey.currentState.pop()
initialRoute: 'initialPageIndex2',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder _builder;
switch (settings.name) {
case 'nextPageForPageIndex2':
_builder = (context) => /*NextPageForPageIndex2()*/;
break;
case 'initialPageIndex2':
default:
_builder = (context) => /*InitialPageIndex2()*/;
}
return MaterialPageRoute(builder: _builder);
},
transitionDelegate: DefaultTransitionDelegate(),
);
)
],)
);
}
}
class Page1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Text('Page1'),
);
}
}
class Page3 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Text('Page3'),
);
}
}
You have to use the PageView please check the code below, hope it will help you :
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Basic AppBar'),
),
body: PageView(
children: <Widget>[
Page1(),
Page2()
],
pageSnapping: false,
scrollDirection: Axis.horizontal,
physics: BouncingScrollPhysics(),
),
);
}
}
class Page1 extends StatefulWidget {
#override
_Page1State createState() => _Page1State();
}
class _Page1State extends State<Page1> {
#override
Widget build(BuildContext context) {
return Container(
child: Center(child:Text("Page 1")),
color: Colors.red,
);
}
}
class Page2 extends StatefulWidget {
#override
_Page2State createState() => _Page2State();
}
class _Page2State extends State<Page2> {
#override
Widget build(BuildContext context) {
return Container(
child: Center(child:Text("Page 2")),
color: Colors.blueAccent,
);
}
}

Animate resizing a ListView item on tap

I have a multi-line Text() inside a ListView item.
By default I only want to show 1 line. When the user taps this item i want it to show all lines. I achieve this by setting the maxLines property of the Text-Widget dynamically to 1 or null.
This works great, but the resizing occurs immediatly but I want to animate this transition.
Here is some example code:
class ListPage extends StatelessWidget {
const ListPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('List Example'),
),
body: ListView.separated(
itemBuilder: (context, index) {
return ListItem();
},
itemCount: 3,
separatorBuilder: (_, int index) => Divider(),
),
);
}
}
class ListItem extends StatefulWidget {
ListItem({Key key}) : super(key: key);
#override
_ListItemState createState() => _ListItemState();
}
class _ListItemState extends State<ListItem> {
bool _expanded;
#override
void initState() {
_expanded = false;
super.initState();
}
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
setState(() {
_expanded = !_expanded;
});
},
child: Text(
'Line1\nLine2\nLine3',
maxLines: _expanded ? null : 1,
softWrap: true,
style: const TextStyle(fontSize: 22),
),
);
}
}
I also already tried using an AnimatedSwitcher like this:
class ListPage extends StatelessWidget {
const ListPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('List Example'),
),
body: ListView.separated(
itemBuilder: (context, index) {
return ListItem();
},
itemCount: 3,
separatorBuilder: (_, int index) => Divider(),
),
);
}
}
class ListItem extends StatefulWidget {
ListItem({Key key}) : super(key: key);
#override
_ListItemState createState() => _ListItemState();
}
class _ListItemState extends State<ListItem> {
bool _expanded;
Widget _myAnimatedWidget;
#override
void initState() {
_expanded = false;
_myAnimatedWidget = ExpandableText(key: UniqueKey(), expanded: _expanded);
super.initState();
}
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
setState(() {
_expanded = !_expanded;
_myAnimatedWidget =
ExpandableText(key: UniqueKey(), expanded: _expanded);
});
},
child: AnimatedSwitcher(
duration: Duration(milliseconds: 2000),
child: _myAnimatedWidget,
),
);
}
}
class ExpandableText extends StatelessWidget {
const ExpandableText({Key key, this.expanded}) : super(key: key);
final expanded;
#override
Widget build(BuildContext context) {
return Text(
'Line1\nLine2\nLine3',
maxLines: expanded ? null : 1,
softWrap: true,
style: const TextStyle(fontSize: 22),
);
}
}
This animates the Text-Widget but the ListView-Row still resizes immediatly.
What is my mistake? Is the approach of setting the maxLines property maybe wrong for my problem?
Thanks for your help !
Have a great day !
Thanks to Joao's comment I found the right answer:
I just had to wrap my Widget inside the AnimatedSize() widget. That's all :)