I have a page where the camera starts and I can record a video. The problem is when I rotate the camera. Then I end up in the main page. This also happens if I navigate to other pages and the change orientation.
Before adding the camera feature I enforced portrait mode so I did not discover this behaviour.
How can I avoid Navigator.pop when the orientation changes?
I am btw a rookie in Flutter.
EDIT: I have tried some different approaches, like adding WillPopScope. However, that is not triggered when rotating the camera.
It does work to navigate to the camera view using this approach:
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => CameraView()),
(Route<dynamic> route) => false,
);
But then there is no back button...
How can this be so complicated?
This is how I add navigation in the HomePage:
Widget startResultButton() {
return Padding(
padding: const EdgeInsets.all(5),
child: FractionallySizedBox(
widthFactor: _buttonWidthFactor,
heightFactor: _buttonHeightFactor,
child: TextButton(
style: getStyle(buttonColor),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return const ResultsMenuView();
},
),
).then((value) => _updateDisplay());
},
child: Row(
children: [
Flexible(child: getSvg("prev_sessions.svg")),
const Flexible(
child: Text(
"Analyse results",
textScaleFactor: textFactor,
)),
],
mainAxisAlignment: MainAxisAlignment.start,
),
),
),
);
}
And the ResultMainView looks like:
class ResultsMenuView extends StatelessWidget {
const ResultsMenuView({Key? key}) : super(key: key);
final double _buttonWidth = 0.9;
final double _heightFactor = 0.6;
Widget getBody() {
var sessionChart = Flexible(
child: NavigationButton(
"Session Overview",
buttonColor,
const SessionOverviewChart(),
_buttonWidth,
_heightFactor,
"overall_results.svg"),
);
var detailedChart = Flexible(
child: NavigationButton(
"Detailed Scores",
buttonColor,
const DetailedChartView(),
_buttonWidth,
_heightFactor,
"detailed_results.svg"),
);
var tableView = Flexible(
child: NavigationButton("History", buttonColor, const TableView(),
_buttonWidth, _heightFactor, "prev_sessions.svg"),
);
var trendCurveButton = Flexible(
child: NavigationButton(
"Trend Curve",
buttonColor,
const TrendCurveView(),
_buttonWidth,
_heightFactor,
"trend_curve.svg"),
);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [sessionChart, detailedChart, trendCurveButton, tableView],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: buttonColor,
title: const Text("Results Menu"),
),
body: SafeArea(
child: getBody(),
),
backgroundColor: backgroundColor,
);
}
}
Related
I'm writing an audio player. Like most media players (Youtube, Spotify, etc), I want a "remote" overlay on the screen while media is playing. No matter what the user is doing, they should be able to control the media.
I accomplished that with a Stack under MaterialApp
MaterialApp(
title: 'MyApp',
navigatorObservers: [gRouteObserver],
routes: appRoutes,
builder: (context, child) {
return Stack(children: [
child!,
Positioned(
bottom: 55,
width: MediaQuery.of(context).size.width,
child: StatefulBuilder(builder: (context, _setState) {
gPlayer.widgetSBRefresher = _setState;
return gPlayer.started ? gPlayer.widget : const SizedBox(height: 0);
}))
]);
});
gPlayer.widget references this
class MiniPlayer extends StatefulWidget {
const MiniPlayer({Key? key}) : super(key: key);
#override
State<MiniPlayer> createState() => MiniPlayerState();
}
class MiniPlayerState extends State<MiniPlayer> with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(context) {
super.build(context);
return Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: Colors.black,
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Column(
children: [
Material(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AvatarAlone(id: gPlayer.current!.owner),
Expanded(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Text(gPlayer.playing
? "Now Playing"
: "Paused"),
),
),
// here is the code I'll
// be talking about -->
IconButton(
color: Colors.white,
iconSize: 20,
icon: const Icon(MyIcons.bookmark),
onPressed: gPlayer.bookmarkBuilder,
),
InkWell(child: Icon(gPlayer.playing ? MyIcons.pauseCircle : MyIcons.playCircle, size: 50), onTap: gPlayer.playPause)
],
),
),
],
),
),
),
));
}
refresh() {
setState(() {});
}
}
I used a code comment to point out this icon button.
IconButton(
color: Colors.white,
iconSize: 20,
icon: const Icon(MyIcons.bookmark),
onPressed: gPlayer.bookmarkBuilder,
),
So, when this widget is open and the app is on the home route ("/"), I can do
bookmarkBuilder() {
Scaffold.of(gScaffApp.currentContext!).openDrawer();
}
and it will open the drawer.
I've attached the same drawers to all my routes' scaffolds.
When other routes are up, with their own scaffolds, I want bookmarkBuilder to open the drawer on the topmost route. But I can't quite figure out how.
So I have a working solution to this, but I don't love it.
I created a global variable, gScaffs, with gScaffApp as the first element.
List<GlobalKey<ScaffoldState>> gScaffs = [gScaffApp];
My secondary routes all use the same base scaffold widget
class _CardScaffoldState extends State<CardScaffold> {
#override
initState() {
super.initState();
gScaffs.add(GlobalKey());
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(key: gScaffs.last,
drawer: DrawerBookmarks()
...
And the dispose method looks like this.
#override
dispose() {
super.dispose();
gScaffs.removeLast();
}
And then, in my bookmarkBuilder function, I have this.
It's not clear to me why, but gScaffApp needs the drawer triggered one way, while the CardScaffolds need the drawer triggered the other way.
bookmarkBuilder() {
if (gScaffApp == gScaffs.last) {
Scaffold.of(gScaffApp.currentContext!).openDrawer();
} else {
gScaffs.last.currentState!.openDrawer();
}
}
I want this type of bottom menu in flutter. I scroll anywhere on screen bottom menu will appear. And when I scroll vertically down it will disappear and on slide up it will appear
For that you can use showModalBottomSheet for this type of widgets.
Sample code:
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
child: const Text('showModalBottomSheet'),
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 200,
color: Colors.amber,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Modal BottomSheet'),
ElevatedButton(
child: const Text('Close BottomSheet'),
onPressed: () => Navigator.pop(context),
)
],
),
),
);
},
);
},
),
);
}
}
Here are the good package which is useful to get pages as you want:
modal_bottom_sheet
How you can use it:
showMaterialModalBottomSheet(
context: context,
builder: (context) => Container(),
)
For more attributes, you can visit documentation here
Example Link : Modal Bottom Sheet
How i pass data from showmodalbottomsheet to previous page. Below is the sample code, what i have tried is there is a button when i click it displays modalbottomsheet and when i click on Done button it should pass 1 value to previous page and also i have added setState on onTap press but still it is not changing on previous page unless i click on Flutter Hot Reload. How to solve this issue?
class TestPage extends StatefulWidget {
const TestPage({Key? key}) : super(key: key);
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
String textResult = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(textResult),
TextButton(
onPressed: () {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(24.0),
topLeft: Radius.circular(24.0))),
context: context,
builder: (_) => StatefulBuilder(
builder: (BuildContext context, setState) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
setState(() {
textResult = "1";
});
Navigator.pop(context);
},
child: Text(
'Done',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold),
),
),
],
),
),
],
),
);
}),
);
},
child: Text('Press'))
],
),
),
),
),
);
}
}
#Canada2000's answer is ok. I'm just extending it.
The use StatefulBuilder inside showDialog() or showModalBottomSheet()+... is these widgets are separated from current context tree.
We use StatefulBuilder when we like to show changes on dialogs. StatefulBuilder has its own stateā¾ builder: (BuildContext context, setState) and calling setState is using StatefulBuilder's setstate.
Now let's say you want to change both UI, to do that you need to simply rename StatefulBuilder's state to something like SBsetState inside builder as #Canada2000 said.
to update the _TestPageState use setState((){})
to update on dialog UI, use StatefulBuilder's state like SBsetState((){})
You may can simple ignore StatefulBuilder if you don't want to show any changes on dialog.
Your widget
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget {
const TestPage({Key? key}) : super(key: key);
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
String textResult = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("text Result ${textResult}"),
TextButton(
onPressed: () {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(24.0),
topLeft: Radius.circular(24.0))),
context: context,
builder: (_) => StatefulBuilder(
builder: (BuildContext context, setStateBTS) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
setState(() {
textResult = "1";
});
Navigator.pop(context);
},
child: Text(
'Done',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold),
),
),
],
),
),
],
),
);
}),
);
},
child: Text('Press'))
],
),
),
),
),
);
}
}
Try to change this line:
builder: (BuildContext context, setState) {
with this line
builder: (BuildContext context, StateSetter setStateModal) {
Explanation:
The outer widget in the build (Scaffold and its content) and the widget inside the showModalBottomSheet have different setState( ). You have to make sure you call the outer one when you need to update parts of that widget. The idea is to setState( ) of widgets outside your showModalBottomSheet. To be able to do that, you can give the two setState() different names so you know which one you are calling: StateSetter setStateModal
I am a beginner in Flutter. I am trying to add a new list item widget to screen when floating action button is pressed. How do I achieve this?
I am trying to create a list of items. When the floating action button is clicked, a dialog box is prompted and user is asked to enter details. I want to add a new list item with these user input details.
This is my input_page.dart file which I am calling in main.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class MedPage extends StatefulWidget {
#override
_MedPageState createState()=> _MedPageState();
}
class _MedPageState extends State<MedPage> {
Future<String>createAlertDialog(BuildContext context) async{
TextEditingController customController= new TextEditingController();
return await showDialog(context: context,builder: (context) {
return AlertDialog(
title: Text("Name of the Pill"),
content: TextField(
controller: customController,
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text("OK"),
onPressed: (){
Navigator.of(context).pop(customController.text.toString()); // to go back to screen after submitting
}
)
],
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My med app'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget> [
Expanded(
child: ListView(
padding: const EdgeInsets.all(8),
children: <Widget>[
ReusableListItem(Color(0xFFd2fddf),"Name 1"),
ReusableListItem(Colors.orange,"Name 2"),
ReusableListItem(Color(0xFF57a1ab), "Name 3"),
],
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
print("Clicked");
createAlertDialog(context).then((onValue){
print(onValue);
setState(() {
});
});
},
child: Icon(Icons.add),
),
);
}
}
class ReusableListItem extends StatelessWidget {
ReusableListItem(this.colour,this.pill);
Color colour;
String pill;
#override
Widget build(BuildContext context) {
return Container(
height: 50,
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: colour,
borderRadius: BorderRadius.circular(15.0)
),
child: Center(
child: Text(pill)
),
);
}
}
You don't need to change much in your code, maintain a variable that stores the values entered to be able to show them in the list. You should use Listview.builder() in order to dynamically render the items.
Here's your code:
class MedPage extends StatefulWidget {
#override
_MedPageState createState() => _MedPageState();
}
class _MedPageState extends State<MedPage> {
List<String> items = [];
Future<String> createAlertDialog(BuildContext context) async {
TextEditingController customController = new TextEditingController();
return await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Name of the Pill"),
content: TextField(
controller: customController,
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop(customController.text
.toString()); // to go back to screen after submitting
})
],
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My med app'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: ListView.builder(
itemBuilder: (context, index) {
return ReusableListItem(Color(0xFFd2fddf), items[index]);
},
itemCount: items.length,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
print("Clicked");
createAlertDialog(context).then((onValue) {
// print(onValue);
setState(() {
items.add(onValue);
});
});
},
child: Icon(Icons.add),
),
);
}
}
class ReusableListItem extends StatelessWidget {
ReusableListItem(this.colour, this.pill);
final Color colour;
final String pill;
#override
Widget build(BuildContext context) {
return Container(
height: 50,
margin: const EdgeInsets.all(8),
decoration:
BoxDecoration(color: colour, borderRadius: BorderRadius.circular(15.0)),
child: Center(child: Text(pill)),
);
}
}
Firstly you need to use ListView.builder() rather than ListView because you have dynamic content. Also you need to hold your items in a list.
// create a list before
ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return Text(list[index]);
}
)
When you click on FloatingActionButton() you will call AlertDialog() method.
FloatingActionButton(
onPressed: (){
AlertDialog(
content: Form(), // create your form here
actions: [
// add a button here
]
)
})
This method will show a dialog(you will add a form inside of the dialog). When the user completes the form(after clicking the button) you will add a new object to the list and update the state with setState({})
onPressed: (){
setState({
// add new object to the list here
});
Navigator.pop(context); // this will close the dialog
}
Not sure what the issue is here but getting a basic login page setup functionally and after the page switches my sign in or signout buttons dont work (but the FAB button does). Here is my signin code for the button:
RaisedButton(
splashColor: Colors.grey,
onPressed: isLoading
? null
: () => _signInWithGoogle(context),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40)),
highlightElevation: 0,
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 0, 10),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image(
Image: AssetImage("images/google_logo.png"),
height: 35.0),
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
'Sign in with Google',
style: TextStyle(
fontSize: 20,
color: Colors.grey,
),
),
)
],
),
),
),
My main class:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<AuthService>(
create: (_) => AuthServiceAdapter(
initialAuthServiceType: AuthServiceType.firebase,
),
dispose: (_, AuthService authService) => authService.dispose(),
),
],
child: AuthWidgetBuilder(
builder:
(BuildContext context, AsyncSnapshot<FirebaseUser> userSnapshot) {
return MaterialApp(
home: AuthWidget(userSnapshot: userSnapshot),
routes: {
HomeScreen.id: (context) => HomeScreen(
title: 'Mythero',
),
},
debugShowCheckedModeBanner: false,
);
},
),
);
}
}
The auth widget:
class AuthWidget extends StatelessWidget {
const AuthWidget({Key key, #required this.userSnapshot}) : super(key: key);
final AsyncSnapshot<FirebaseUser> userSnapshot;
#override
Widget build(BuildContext context) {
if (userSnapshot.connectionState == ConnectionState.active) {
return userSnapshot.hasData
? HomeScreen(
title: 'Mythero',
)
: SignInPageBuilder();
}
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
My signout button
child: RaisedButton(
onPressed: () async {
await auth.signOut();
},
child: Text(
'Sign Out',
),
),
The login and sign out code works but when it navigates to another and then I press one of the buttons it just prints 'Dw' in the terminal output. If I refresh the page then it works just fine.