Open hiroshihorie opened 4 years ago
Hi @HiroshiHorie
can you please provide your flutter doctor -v
?
Also, to better address the issue, would be helpful if you could post a complete minimal code sample to reproduce the problem
Thank you
It's also similar to the case of using AnimatedSwitcher with IndexedStack #39398 #48217
[✓] Flutter (Channel stable, v1.17.0, on Mac OS X 10.15.5 19F72f, locale ja-JP)
• Flutter version 1.17.0 at /Users/hiroshi/flutter
• Framework revision e6b34c2b5c (6 days ago), 2020-05-02 11:39:18 -0700
• Engine revision 540786dd51
• Dart version 2.8.1
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
• Android SDK at /Users/hiroshi/Library/Android/sdk
• Platform android-29, build-tools 29.0.2
• Java binary at: /Users/hiroshi/Library/Application
Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/192.6392135/Android
Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 11.4.1)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Xcode 11.4.1, Build version 11E503a
• CocoaPods version 1.9.1
[✓] Android Studio (version 3.6)
• Android Studio at /Users/hiroshi/Library/Application
Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/192.6392135/Android Studio.app/Contents
• Flutter plugin version 45.1.1
• Dart plugin version 192.7761
• Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
[✓] VS Code (version 1.43.0)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.8.1
[✓] Connected device (1 available)
• Android SDK built for x86 • emulator-5554 • android-x86 • Android 10 (API 29) (emulator)
• No issues found!
This is a problem, PageTransitionSwitcher will rebuild the page when switching pages, and twice.
Is there any workaround for this? It would be nice to use the animation transitions provided by Flutter (e.g. FadeThroughTransition) along with IndexedStack, the recommended way to maintain state when switching between top-level pages with a bottom navigation bar.
I am having the same problem as @doppio, I would really like to be able to use this with a bottom navigation bar.
I think the only way to make this work is to wait flutter team rewrite that animations package 😂
I've had better luck using the animations package along with the new Pages API where I am able to set the page route transition when using a BottomNavigationBar
or NavigationRail
.
Ping creator @goderbauer , any idea how to fix it?
I recently ran into this issue and this is the best solution I've come up with so far. It's not ideal as it still involves two rebuilds, but the state is maintained:
import 'package:flutter/widgets.dart';
/// Based on the PageTransitionSwitcher from the animations package, this widget
/// allows you to transition between an array of widgets using entry and exit
/// animations whilst maintaining their state.
class IndexedTransitionSwitcher extends StatefulWidget {
/// Creates an [IndexedTransitionSwitcher].
const IndexedTransitionSwitcher({
@required this.index,
@required this.children,
@required this.transitionBuilder,
this.reverse = false,
this.duration = const Duration(milliseconds: 300)});
/// The index of the child to show.
final int index;
/// The widgets to switch between.
final List<Widget> children;
/// A function to wrap the child in primary and secondary animations.
///
/// When the index changes, the new child will animate in with the primary
/// animation, and the old widget will animate out with the secondary
/// animation.
final Widget Function(
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation
) transitionBuilder;
/// The duration of the transition.
final Duration duration;
/// Whether or not the transition should be reversed.
///
/// If true, the new child will animate in behind the oWld child with the
/// secondary animation running in reverse, whilst the old child animates
/// out with the primary animation playing in reverse.
final bool reverse;
@override
_IndexedTransitionSwitcherState createState() => _IndexedTransitionSwitcherState();
}
class _IndexedTransitionSwitcherState extends State<IndexedTransitionSwitcher>
with TickerProviderStateMixin {
List<_ChildEntry> _childEntries;
@override
void initState() {
super.initState();
// Create the page entries
this._childEntries = widget.children
.asMap().entries
.map((entry) => _createPageEntry(entry.key, entry.value))
.toList();
}
@override
void didUpdateWidget(IndexedTransitionSwitcher oldWidget) {
super.didUpdateWidget(oldWidget);
// Transition if the index has changed
if (widget.index != oldWidget.index) {
_ChildEntry newChild = _childEntries
.where((entry) => entry.index == widget.index).first;
_ChildEntry oldChild = _childEntries
.where((entry) => entry.index == oldWidget.index).first;
// Animate the children
if (widget.reverse) {
// Animate in the new child
newChild.primaryController.value = 1;
newChild.secondaryController.reverse(from: 1);
// Animate out the old child and unstage it when the animation is complete
oldChild.secondaryController.value = 0;
oldChild.primaryController.reverse(from: 1).then((value) => setState(() {
oldChild.onStage = false;
oldChild.primaryController.reset();
oldChild.secondaryController.reset();
}));
} else {
// Animate in the new child
newChild.secondaryController.value = 0;
newChild.primaryController.forward(from: 0);
// Animate out the old child and unstage it when the animation is complete
oldChild.primaryController.value = 1;
oldChild.secondaryController.forward().then((value) => setState(() {
oldChild.onStage = false;
oldChild.primaryController.reset();
oldChild.secondaryController.reset();
}));
}
// Reorder the stack and set onStage to true for the new child
_childEntries.remove(newChild);
_childEntries.remove(oldChild);
_childEntries.addAll(widget.reverse ? [newChild, oldChild]
: [oldChild, newChild]);
newChild.onStage = true;
}
}
_ChildEntry _createPageEntry(int index, Widget child) {
// Prepare the animation controllers
final AnimationController primaryController = AnimationController(
value: widget.index == index ? 1.0 : 0,
duration: widget.duration,
vsync: this,
);
final AnimationController secondaryController = AnimationController(
duration: widget.duration,
vsync: this,
);
// Create the page entry
return _ChildEntry(
key: UniqueKey(),
index: index,
primaryController: primaryController,
secondaryController: secondaryController,
transitionChild: widget.transitionBuilder(child, primaryController, secondaryController),
onStage: widget.index == index
);
}
@override
void dispose() {
// Dispose of the animation controllers
for (_ChildEntry entry in _childEntries) {
entry.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) => Stack(
alignment: Alignment.center,
fit: StackFit.expand,
children: _childEntries
.map<Widget>((entry) => Offstage(
key: entry.key,
offstage: !entry.onStage,
child: entry.transitionChild,
))
.toList(),
);
}
/// Internal representation of a child.
class _ChildEntry {
_ChildEntry({
@required this.index,
@required this.key,
@required this.primaryController,
@required this.secondaryController,
@required this.transitionChild,
@required this.onStage
});
/// The child index.
final int index;
/// The key to maintain widget state when moving in the tree.
final Key key;
/// The entry animation controller.
final AnimationController primaryController;
/// The exit animation controller.
final AnimationController secondaryController;
/// The child widget wrapped in the transition.
final Widget transitionChild;
/// Whether or not the child should be rendered.
bool onStage;
/// Dispose of the animation controllers
void dispose() {
primaryController.dispose();
secondaryController.dispose();
}
}
I think that it would be better to:
PageTransitionSwitcher
and IndexedStack
functionalities into someting like AnimatedIndexedStack
in the flutter framework itself. This would also resolve #48217PageTransitionSwitcher
, as it's basically a stripped duplicate of AnimatedSwitcher
, but with reverse
parameterreverse
parameter in them directlyI've managed to make it work using PageStorageKey. So it preserves state and animates using PageTransitionSwitcher.
@matejkramny may i know how you use PageStorageKey
? did you apply straight onto the PageTransitionSwitcher's key? Or on every single child page widget as well?
@sooxt98
.. with PageStorage up the tree somewhere
child: PageTransitionSwitcher(
reverse: condition,
transitionBuilder: (child, animation, animation2) {
return SharedAxisTransition(
animation: animation,
secondaryAnimation: animation2,
transitionType: SharedAxisTransitionType.scaled,
child: child,
);
},
child: condition
? PageTwo()
: PageOne(
key: PageStorageKey<String>('somekey'),
),
),
For my usecase, PageTwo is a detail page from PageOne. Switching back and forth between these pages, I needed PageOne to be kept "alive". The widget is rebuilt but is actually resurrected from the Storage bucket.
Any update on this one?
Similar issue with tabs (DefaultTabController). It would be great if Flutter provides a simple build-in feature directly to the related widget to keep the state of children. My story in short:
I started with a BottomNavigationBar navigation. But want transition between navigation. I ended up with IndexedStack
to keep the state of each tab / page. Then I found AnimatedSwitcher
to bring some life to it. But somehow it requires the key property for IndexedStack. It works, but the state is no longer preserved between transitions. It is destroyed and created. Then I switched to Tabs, because I want a slide animation anyway and thought tabs will not be destroyed when switching tabs. Ok, this is just my interpretation, as a frontend developer from Angular. ^^ The current Flutter behavior is like lazy load on Angular. But whatever... I think it would be great if Flutter provides an option to keep childrean alive or not (as now). Flutter is great, but it seems that it lacks of a nice page navigation. But I'm new to Flutter. I am currently struggling to implement the basic navigation with transition without losing page state. I want to keep all pages open, but only show each one individually.
Do we have any updates on this, please? This can be a crucial piece of code to bring normal apps to life with some easy-to-use animations.
I want to use PageTransitionSwitcher with IndexedStack to display my tab pages. If I don't set a ValueKey to IndexedStack, it doesn't animate but the IndexStack works well. If I do set a ValueKey, it animates but IndexedStack rebuilds all the time and causes the tab page's state to be lost. Which defeats the purpose of using IndexedStack.
I did try to use AutomaticKeepAliveClientMixin, but still rebuilds the tabs, and I just want to avoid it anyways since IndexedStack keeps my code cleaner.
I believe there are many users that want to use this combination since it is a very common pattern and it shouldn't be so complicated.
What is the best solution?
Regards Hiroshi