I have a problem with Dismissible widget. I use that to close actual page by calling a function:
onDismissed: (_) => Navigator.of(context).pop().
so what I did: wrap whole scaffold by Dismissible.
Everything worked fine until delivered to the page state with bool information about the logged in user (admin or no).
Now i get error like this:
FlutterError (A dismissed Dissmisible widget is still part of the
tree. Make sure to implement the onDismissed handler and to
immediately remove the Dismissible widget from the application once
that has fired.)
Screen: ErrorScreen
I tried "key" changes but it didn't help.
Page code with Dismissible:
class OtherDetailsPage extends StatelessWidget {
const OtherDetailsPage({required this.id, Key? key}) : super(key: key);
final String id;
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
OtherDetailsCubit(OtherRemoteDataSource())..getDetailsWithID(id),
child: BlocBuilder<OtherDetailsCubit, OtherDetailsState>(
builder: (context, details) {
final detail = details.detailsState;
return BlocBuilder<RootCubit, RootState>(
builder: (context, root) {
bool? admin = root.admin;
if (admin == null || detail == null) {
return const LoadingPage();
}
return Dismissible(
key: const Key('key'),
// key: Key(UniqueKey().toString()),
direction: DismissDirection.down,
onDismissed: (_) => Navigator.of(context).pop(),
child: Scaffold(
body: Column(
children: [
Header(detail: detail),
const SizedBox(
height: 20,
),
DetailsDescription(detail: detail),
const SizedBox(
height: 50,
),
admin
? SwitchValueButton(id: id, detail: detail)
: const SizedBox(
height: 1,
),
const AnimatedArrowDown()
],
)),
);
},
);
},
),
);
}
}
Related
I have CustomScrollView with a SliverGrid with different widget types inside. I have a widget that appears after the stream content ends and other content will load. When this widget appears, I want to shortly pin it on the screen and disable the scrolling for 3 seconds.
To simplify it, I skipped the sliver delegates and summarizes my widget tree like this:
CustomScrollView{
controller: scrollController,
slivers: [
SliverAppBar(),
SliverStreamGrid(
children: [
ProductTile(),
ProductTile(),
ProductTile(),
// End of Available Products
EndOfProductsInfoWidget(), // should be pinned on screen for 3 seconds
SomeOtherProductTile(),
SomeOtherProductTile(),
SomeOtherProductTile(),
]
)
]
}
I am using visibility_detector to detect visibility of the widget and SliverPinnedHeader from sliver_tools package.
The issue lies when our widget is visible I am using a short delay and then disabling scroll event for 3 seconds, you can use global key for this and have more precious output.
CustomScrollView's physics:NeverScrollableScrollPhysics() used to disable scroll event.
class SliverPinTHEx extends StatefulWidget {
const SliverPinTHEx({Key? key}) : super(key: key);
#override
State<SliverPinTHEx> createState() => _SliverPinTHExState();
}
class _SliverPinTHExState extends State<SliverPinTHEx> {
bool? _showPinMessage;
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
physics: _showPinMessage == true
? const NeverScrollableScrollPhysics()
: null,
slivers: [
list(),
if (_showPinMessage != false)
SliverPinnedHeader(
child: VisibilityDetector(
onVisibilityChanged: (info) async {
if (info.visibleFraction == 1.0) {
debugPrint("disable scroll");
await Future.delayed(const Duration(seconds: 1));
setState(() {
_showPinMessage = true;
});
Future.delayed(const Duration(seconds: 3)).then((value) {
setState(() {
_showPinMessage = false;
});
});
}
},
key: const Key('my-widget-key'),
child: Container(
height: 70,
color: Colors.amber,
child: const Text("pinned widget"),
),
)),
list()
],
),
);
}
SliverList list() {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return SizedBox(
height: 50,
child: Text(
index.toString(),
),
);
},
childCount: 55,
),
);
}
}
Am a completely new flutter dev. I am trying to save a document from a queried firestore list on another saved documents page like an add to cart functionality. Am passing doc id as arguments to another page from firestore so that I get data based on the previous selection. Now how can I send the firestore reference and save it to the other screen without navigating to it so that users are able to save their favorite docs on another page and access them? Here is my Assignment page that lists the docs based on the previous selection.
class Assignments extends StatelessWidget {
final String programId;
final String yearId;
final String semesterId;
final String courseId;
const Assignments(
{Key key, this.programId, this.yearId, this.semesterId,
this.courseId})
: super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar2(title: 'Assigment'.toUpperCase(), ),
body: Column(
children: [
Expanded(
child: ContentArea(
addPadding: false,
child: StreamBuilder(
stream:
getAssignment(programId, yearId, semesterId, courseId),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(
color: kOnSurfaceTextColorYellow),
);
}
return ListView.separated(
padding: UIParameters.screenPadding,
shrinkWrap: true,
itemCount: snapshot.data.docs.length,
itemBuilder: (BuildContext context, int index) {
final data = snapshot.data.docs[index];
return DisplayCard(
title: data['nameOfAssignment'],
icon: Icons.add,
// Here is the action i want that should save the documment to
// the SavedPage empty list without navigating to it
onTapIconSave: (){}
onTap: () => Get.to(Pdf(
nameOfAssignment: data['nameOfAssignment'],
pdfUrl: data['pdfUrl'],
)),
);
},
separatorBuilder: (BuildContext context, int index) {
return const SizedBox(
height: 10,
);
},
);
})),
),
],
),
);
}
}
Here is the SavedPage which may be similar to the cart page. Am not sure what to do in order to save the Document from the Assignment Page in a Dynamic growable list
class Saved extends StatefulWidget {
const Saved({ Key key }) : super(key: key);
#override
State<Saved> createState() => _SavedState();
}
class _SavedState extends State<Saved> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const CustomAppBar2(title: 'Saved'),
body: Column(
children: [],
),
);
}
}
You can add a state management package like Provider or Bloc, also you could save your data in your local database and access them from there. I recommend Provider, easy to use, and its what you need.
I want to create a dropdown menu on flutter where the handler button that opens the dropdown uses just an icon and the menu list opened by it uses an icon and a text.
I almost manage to create it, as you can check on the following screenshots:
Closed
Opened
I'm struggling with the opened width, so my question is how to give the opened menu enough width and keep the handler button on its current width.
Notice that I want the dropdown to be at the end of the Row, so consider this black box to be an area of something else, nothing important.
I'm adding the relevant code below and the complete code on the following links.
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Question Dropdown",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(
optionStream: BehaviorSubject<Option>(),
),
);
}
}
class HomePage extends StatelessWidget {
final BehaviorSubject<Option> optionStream;
const HomePage({
Key? key,
required this.optionStream,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Question Dropdown"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Expanded(
child: Container(
height: 48,
color: Colors.black,
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: StreamBuilder<Option>(
initialData: Option.A,
stream: optionStream,
builder: (context, snapshot) {
final option = snapshot.data ?? Option.A;
return _dropDownMenu(context, option);
},
),
),
],
),
],
),
),
);
}
Widget _dropDownMenu(
BuildContext context,
Option option,
) {
const items = Option.values;
return DropdownButtonHideUnderline(
child: DropdownButton<Option>(
value: option,
selectedItemBuilder: (context) =>
items.map((e) => _dropdownHandler(context, e)).toList(),
items: items.map((e) => _dropdownItem(context, e)).toList(),
onChanged: (e) => optionStream.add(e ?? Option.A),
),
);
}
OptionsItemHelper _dropDownItemData(
BuildContext context,
Option option,
) {
Widget icon;
String text;
switch (option) {
case Option.A:
icon = const Icon(Icons.ac_unit);
text = "An option";
break;
case Option.B:
icon = const Icon(Icons.baby_changing_station);
text = "Best option";
break;
case Option.C:
icon = const Icon(Icons.cake_sharp);
text = "Closest option";
break;
case Option.D:
icon = const Icon(Icons.dashboard);
text = "Dumb option";
break;
}
return OptionsItemHelper(text, icon);
}
Widget _dropdownHandler(
BuildContext context,
Option option,
) {
final helper = _dropDownItemData(context, option);
return helper.icon;
}
DropdownMenuItem<Option> _dropdownItem(
BuildContext context,
Option option,
) {
final helper = _dropDownItemData(context, option);
return DropdownMenuItem<Option>(
value: option,
child: Row(
children: [
helper.icon,
const SizedBox(width: 16),
Text(helper.text),
],
),
);
}
}
enum Option {
A,
B,
C,
D,
}
class OptionsItemHelper {
final String text;
final Widget icon;
OptionsItemHelper(
this.text,
this.icon,
);
}
Complete code on Github
Complete code on Gitlab
I did find a workaround using GestureDetector and showMenu, I'm sharing here and pushing to the repo as "workaround" commit in case you need the same as I need now, I'm keeping the question without answer in case someone finds a better way using the dropdown.
The new dropDownMenu function
Widget _dropDownMenu(
BuildContext context,
Option option,
) {
const items = Option.values;
return GestureDetector(
onTapDown: (details) async {
final offset = details.globalPosition;
final newOption = await showMenu(
context: context,
position: RelativeRect.fromLTRB(offset.dx, offset.dy, 0, 0),
items: items.map((e) => _dropdownItem(context, e, option)).toList(),
);
if (newOption != null) {
optionStream.add(newOption);
}
},
child: _dropdownHandler(context, option),
);
}
and the new dropdownItem function.
PopupMenuEntry<Option> _dropdownItem(
BuildContext context,
Option option,
Option selected,
) {
final helper = _dropDownItemData(context, option);
return CheckedPopupMenuItem<Option>(
value: option,
checked: option == selected,
child: Row(
children: [
Expanded(child: Container()),
Text(helper.text),
const SizedBox(width: 16),
helper.icon,
],
),
);
}
How it looks like
Closed
Opened
Bigger Screen
I have below screen to implement, The Problem I am having is that I don't know how to show the snackbar at the bottom of the container. It's showing snackbar at the bottom of the screen, but i want it like below:
Note : The Snackbar in the image is shown with text 'Whoray, Right Answer.'
Adding Code :
SingleChildScrollView(
child: BlocBuilder<QuizControllerCubit, QuizControllerState>(
builder: (context, state) {
if (state is OnWrongAnswerGiven) {
SnackBarCustomWidget.showSnackBar(
context,
SnackBarWidget.getSnackBar(context, state.message),
);
} else if (state is OnRightAnswerGiven) {
SnackBarCustomWidget.showSnackBar(
context,
SnackBarWidget.getSnackBar(context, state.message),
);
}
return SnackBarCustomWidget(
child: Builder(
builder: (context) => Column(
mainAxisSize: MainAxisSize.min,
children: [
QuizTitleWidget(title: question.name),
Container(
constraints: BoxConstraints(
minHeight: MediaQueryUtils(context).height * 0.5,
maxHeight: MediaQue
let me give you a widget for that
class MySnackBarStack extends StatefulWidget {
final Widget child;
const MySnackBarStack({Key key, this.child}) : super(key: key);
#override
_MySnackBarStackState createState() => _MySnackBarStackState();
static void showSnackBar(BuildContext context,SnackBar snackBar){
context.findAncestorStateOfType<_MySnackBarStackState>()._showSnackBar(snackBar);
}
static void hideSnackBar(BuildContext context){
context.findAncestorStateOfType<_MySnackBarStackState>()._hideSnackBar();
}
}
class _MySnackBarStackState extends State<MySnackBarStack> {
Widget snackBar = SizedBox();
void _showSnackBar(SnackBar snackBar){
setState(() {
this.snackBar = snackBar;
});
}
void _hideSnackBar(){
setState(() {
snackBar = SizedBox();
});
}
#override
Widget build(BuildContext context) {
return Stack(
children: [
widget.child,
snackBar,
],
);
}
}
now Use it like this in any build method
//you cannot access MySnackBarStack here and up
MySnackBarStack(
child: Builder(
//you can only access MySnackBarStack here and down
builder: (newContext){
return MyContainer(
onTap:(){
MySnackBarStack.showSnackBar(newContext, snackBar);
}
);
},
),
)
I want switch status with animation in CustomScrollView, but it throw error.
class SliverAnimatedSwitcher extends StatefulWidget {
final state;
const SliverAnimatedSwitcher({Key key, this.state}) : super(key: key);
#override
_SliverAnimatedSwitcherState createState() => _SliverAnimatedSwitcherState();
}
class _SliverAnimatedSwitcherState extends State<SliverAnimatedSwitcher> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
title: Text('SliverAnimatedSwitcher'),
),
_buildContent(),
],
),
);
}
get state => widget.state;
Widget _buildContent() {
var content;
if (state.isNotEmpty == true) {
content = SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
var item = state.items[index];
return ListTile(
title: Text(item.title),
);
},
childCount: state.items.length,
),
);
} else if (state.isError) {
content = SliverFillRemaining(
key: Key('error'),
child: Container(alignment: Alignment.center, child: Text('Error')),
);
} else if (state.isLoading) {
content = SliverFillRemaining(
key: Key('loading'),
child: Container(alignment: Alignment.center, child: Text('Loading')),
);
} else {
content = SliverFillRemaining(
key: Key('empty'),
child: Container(alignment: Alignment.center, child: Text('Empty')),
);
}
return AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: content,
);
}
}
There's a sliver_tools package on pub.dev that has a SliverAnimatedSwitcher. You'd use it like this:
SliverAnimatedSwitcher(
duration: kThemeAnimationDuration,
child: content,
)
It is because sliver widget is not capable with Stack which is using as layout builder inside AnimatedSwitcher.
I found a temporary solution. Though the result is not equal to the original one (only show the last widget in the result), but I think it is acceptable for me now with such little effort.
First create a SliverAnimatedSwitcher exact the same code as AnimatedSwitcher here
Modify the Stack part to return the currentChild only (defaultLayoutBuilder)
Change FadeTransition to SliverFadeTransition (defaultTransitionBuilder)
.
static Widget defaultLayoutBuilder(Widget currentChild, List<Widget> previousChildren) {
return currentChild;
}
.
static Widget defaultTransitionBuilder(Widget child, Animation<double> animation) {
return SliverFadeTransition(
opacity: animation,
sliver: child,
);
}
If someone can find a way to Stack Sliver widgets above each other, the result may be more perfect.