I've got a search-field in the appbar of my application. It's available on every page of the app. When something is selected in the seach-field, it routes to a named page that loads and displays details about the entity. It is also possible to use the search bar from the details page, which means, that it routes to the same page that it's already on. As the id of the entity is part of the url, the route is actually different and as such everything works. But...
When the page is opened, my GetX controller does some initialization, and on leave it disposes of resources such as TextController.
My problem is that when I navigate to the same page, onClose is not called before leaving which means that the resources are not disposed. Rather it open the 'new' page, calls onInit() then calls onClose() and disposes the resources that are in use.
When the action of the seach-field determines that the currentRoute is already on the details-page, it uses Get.offNamed('/details', parameters({id: entityId})), if it is on another page it simply uses Get.toNamed('/details', parameters({id: entityId}))
I've made a little app the illustrates the problem. The search-field has been replaced by buttons for simplicity.
https://flutlab.io/editor/994c9029-47e9-46e2-a0e6-cfd970b82079
To make it fail, try pressing 'To subpage' button twice.
Notice in the console:
Initialized
Initialized
Closed
I was expecting:
Initialized
Closed
Initialized
Closed
For reference this is the code:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
final Random random = Random();
final appBar = AppBar(
// The title text which will be shown on the action bar
title: const Text('Hello'),
actions: [
ElevatedButton(
child: const Text('To Subpage'),
onPressed: () {
if (Get.currentRoute.startsWith('/subpage')) {
Get.offNamed('/subpage',
parameters: {'id': '${random.nextInt(1000)}'});
} else {
Get.toNamed('/subpage',
parameters: {'id': '${random.nextInt(1000)}'});
}
}),
ElevatedButton(
child: const Text('Go Home'),
onPressed: () {
Get.offAllNamed('/', parameters: {'id': '${random.nextInt(1000)}'});
})
],
);
final pages = [
GetPage(name: '/', page: () => RootPage()),
GetPage(name: '/subpage', page: () => SubPage(), binding: PageBinding()),
];
class PageBinding implements Bindings {
#override
void dependencies() {
Get.put(SubPageController());
}
}
class SubPageController extends GetxController {
final TextEditingController myDomController = TextEditingController();
String? id;
#override
void onInit() {
id = Get.parameters['id'];
print('initialized with id $id');
// Now initialize and load a lot of stuff based on id
super.onInit();
}
#override
void onClose() {
print('closed with id $id');
myDomController.dispose(); // This is a problem!
super.onClose();
}
}
void main() => runApp(GetMaterialApp(
getPages: pages,
initialRoute: '/',
));
class RootPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: appBar,
body: const Center(
child: Text(
'Root page',
),
),
);
}
}
class SubPage extends GetView<SubPageController> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: appBar,
body: Center(
child: Column(
children: [
Text(
'Subpage page',
),
TextFormField(controller: controller.myDomController)
],
)),
);
}
}
```
in such cases for you, you can delete (run onClose()) the controller you want simply by using:
Get.delete<SubPageController>();
if you have a tag for that controller:
Get.delete<SubPageController>(tag: yourTagHere);
so in the onPressed of your buttons, you could call to delete the controller from the memory, then call it for your next use:
ElevatedButton(
child: const Text('Go Home'),
onPressed: () {
Get.delete<SubPageController>();
Get.offAllNamed('/', parameters: {'id': '${random.nextInt(1000)}'});
})
this will delete the controller if it's already on, then navigate to that root so it will be called again, and so on.
Related
I'm using syncfusion_flutter_pdfviewer package for my flutter project, and I'm trying to pass a specific page number from my Main Page to my Details page. After that it'll load that PDF page number immediacy when the Detail page is open, but I'm stuck, so any help or suggestion would be really appreciated.
I have tried like this, but I'm getting an error on "jumpToPage"
This expression has a type of 'void' so its value can't be used.
Try checking to see if you're using the correct API; there might be a function or call that returns void you didn't expect. Also check type parameters and variables which might also be void.
body: SfPdfViewer.asset(
'data/hymn_pdf/full-songs.pdf',
controller: _pdfViewerController.jumpToPage(widget.number),
),
Full Details page Code.
class DisplayScreen extends StatefulWidget {
final int number;
const DisplayScreen(this.number);
#override
_DisplayScreen createState() => _DisplayScreen();
}
class _DisplayScreen extends State<DisplayScreen> {
final GlobalKey<SfPdfViewerState> _pdfViewerKey = GlobalKey();
late PdfViewerController _pdfViewerController;
#override
void initState() {
_pdfViewerController = PdfViewerController();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Syncfusion Flutter PdfViewer'),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.arrow_drop_down_circle,
color: Colors.white,
),
onPressed: () {
_pdfViewerController.jumpToPage(widget.number); // I got this from synfusion doc and I'm trying to copy it in my controller but I'm getting an error.
},
),
],
),
body: SfPdfViewer.asset(
'data/hymn_pdf/full-songs.pdf',
controller: _pdfViewerController.jumpToPage(widget.number), //I'm getting an error here
),
);
}
}
You cannot just call jumpToPage when assigning the controller. Wrong example:
body: SfPdfViewer.asset(
'data/hymn_pdf/full-songs.pdf',
controller: _pdfViewerController.jumpToPage(widget.number), // <-- nope
),
You need to call the method after the pdf is loaded. For example, call it in the onDocumentLoaded callback function.
Example code:
SfPdfViewer.asset(
'data/hymn_pdf/full-songs.pdf',
controller: _pdfViewerController,
onDocumentLoaded: (details) { // as soon as the doc is loaded
_pdfController.jumpToPage(3); // jump to page 3
},
)
I am using the following package https://pub.dev/packages/get. Do I need to close my .obs in the onClose of a GetxController? I can't find anything about this in the docs. And looking at my memory it appears that the are being destroyed automatically.
In my understanding of GetX + Flutter so far...
No, you shouldn't have to remove .obs in the close() method of GetxControllers. Disposal of observables from a Controller are done automatically when the Controller is removed from memory.
GetX disposes/removes GetxControllers (and their observables) when the widget in which they are contained are popped off the widget stack / removed from the widget tree (by default, but can be overridden).
You can see this in the override of dispose() methods of various Get widgets.
Here's a snippet of dispose() that's run when GetX widgets are popped/removed:
#override
void dispose() {
if (widget.dispose != null) widget.dispose(this);
if (isCreator || widget.assignId) {
if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) {
GetInstance().delete<T>(tag: widget.tag);
}
}
subs.cancel();
_observer.close();
controller = null;
isCreator = null;
super.dispose();
}
When you use Bindings or Get.to() you're using GetPageRoute's which do cleanup by Route names:
#override
void dispose() {
if (Get.smartManagement != SmartManagement.onlyBuilder) {
WidgetsBinding.instance.addPostFrameCallback((_) => GetInstance()
.removeDependencyByRoute("${settings?.name ?? routeName}"));
}
super.dispose();
}
Test App
Below is a test App you can copy/paste into Android Studio / VSCode and run to watch the debug or run window output for GETX lifecycle events.
GetX will log the creation & disposal of Controllers in and out of memory.
The app has a HomePage and 3 ChildPages using Get Controllers in 3 ways, all which remove itself from memory:
GetX / GetBuilder
Get.put
Bindings
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
// MyCounterBinding().dependencies(); // usually where Bindings happen
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'GetX Dispose Ex',
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GetX Dispose Test'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
child: Text('GetX/Builder Child'),
onPressed: () => Get.to(ChildPage()),
),
RaisedButton(
child: Text('Get.put Child'),
onPressed: () => Get.to(ChildPutPage()),
),
RaisedButton(
child: Text('Binding Child'),
onPressed: () => Get.to(ChildBindPage()),
),
],
),
),
);
}
}
/// GETX / GETBUILDER
/// Creates Controller within the Get widgets
class ChildPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GetX Dispose Test Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('This is the Child Page'),
GetX<ChildX>(
init: ChildX(),
builder: (cx) => Text('Counter: ${cx.counter}', style: TextStyle(fontSize: 20),),
),
GetBuilder<ChildX>(
init: ChildX(),
builder: (cx) => RaisedButton(
child: Text('Increment'),
onPressed: cx.inc,
),
),
],
),
),
);
}
}
/// GET.PUT
/// Creates Controller instance upon Build, usable anywhere within the widget build context
class ChildPutPage extends StatelessWidget {
//final ChildX cx = Get.put(ChildX()); // wrong place to put
// see https://github.com/jonataslaw/getx/issues/818#issuecomment-733652172
#override
Widget build(BuildContext context) {
final ChildX cx = Get.put(ChildX());
return Scaffold(
appBar: AppBar(
title: Text('GetX Dispose Test Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('This is the Child Page'),
Obx(
() => Text('Counter: ${cx.counter}', style: TextStyle(fontSize: 20),),
),
RaisedButton(
child: Text('Increment'),
onPressed: cx.inc,
)
],
),
),
);
}
}
class MyCounterBinding extends Bindings {
#override
void dependencies() {
Get.lazyPut(() => ChildX(), fenix: true);
}
}
/// GET BINDINGS
/// Normally the MyCounterBinding().dependencies() call is done in main(),
/// making it available throughout the entire app.
/// A lazyPut Controller /w [fenix:true] will be created/removed/recreated as needed or
/// as specified by SmartManagement settings.
/// But to keep the Bindings from polluting the other examples, it's done within this
/// widget's build context (you wouldn't normally do this.)
class ChildBindPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
MyCounterBinding().dependencies(); // just for illustration/example
return Scaffold(
appBar: AppBar(
title: Text('GetX Dispose Test Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('This is the Child Page'),
Obx(
() => Text('Counter: ${ChildX.i.counter}', style: TextStyle(fontSize: 20),),
),
RaisedButton(
child: Text('Increment'),
onPressed: ChildX.i.inc,
)
],
),
),
);
}
}
class ChildX extends GetxController {
static ChildX get i => Get.find();
RxInt counter = 0.obs;
void inc() => counter.value++;
}
Notes
Get.to vs. Navigator.push
When using Get.put() in a child widget be sure you're using Get.to() to navigate to that child rather than Flutter's built-in Navigator.push.
GetX wraps the destination widget in a GetPageRoute when using Get.to. This Route class will dispose of Controllers in this route when navigating away / popping the widget off the stack. If you use Navigator.push, GetX isn't involved and you won't get this automatic cleanup.
Navigator.push
onPressed: () => Navigator.push(context, MaterialPageRoute(
builder: (context) => ChildPutPage())),
Get.to
onPressed: () => Get.to(ChildPutPage()),
Based from the code of the super implementation of onClose, by default it does nothing currently.
https://github.com/jonataslaw/getx/blob/7146b6a53c0648104e4f623385deaff055e0036a/lib/get_instance/src/lifecycle.dart#L56
And from the comments, it says:
/// Called before [onDelete] method. [onClose] might be used to
/// dispose resources used by the controller. Like closing events,
/// or streams before the controller is destroyed.
/// Or dispose objects that can potentially create some memory leaks,
/// like TextEditingControllers, AnimationControllers.
/// Might be useful as well to persist some data on disk.
void onClose() {}
from that I think you need to manually close your streams in YourController::onClose() override function.
It appears you can use obs safely when using GetWorkers. Run this code and you'll notice that when you click the buttons a few time there will only be one print per page switch.
void main(){
runApp(GetMaterialApp(home: TestWidget(),));
}
class TestWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text('next'),
onPressed: () => Get.to<SomeWidget>(SomeWidget()),
),
);
}
}
class SomeWidget extends StatelessWidget {
RxBool isSubscribed = false.obs;
SomeWidget() {
ever(isSubscribed, (_) => print('test'));
}
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text('back'),
onPressed: () {
isSubscribed.value = !isSubscribed.value;
Get.back();
},
),
);
}
}
I want my page to hit the API always whenever it enters a page.
For example, I am having 2 screens i.e FirstSCreen and SecondScreen.
In FirstScreen I am calling an API to fetch some data. So if the user navigates from FirstScreen to SecondScreen and then comes back to FirstScreen by pressing the back button It should hit the API in FirstScreen again.
I want to know is there any inbuilt function in flutter where I should call my methods so that it can work every time it enters the screen. I have tried using didUpdateWidget() but it is not working the way I want.
initState() is also called only once the widget is loaded ..
Please explain me
You can use async await for it. Let's say you have a button that change the route.
onPressed: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SecondScreen(),
),
);
ApiCall();
},
The ApiCall function will call only when user push the back on the second screen.
I have an example that might help. However, it might not cover all scenarios where your FirstScreen will always call a specific function whenever the page is displayed after coming from a different page/screen, or specifically resuming the app (coming from background -- not to be confused when coming from another screen or popping).
My example however, will always call a function when you come back from a specific screen, and you can re-implement it to other navigation functions to ensure that a specific function is always called when coming back to FirstScreen from other screens.
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
home_page.dart
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: RaisedButton(
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => SecondPage()))
.then((value) => _reload(value)),
child: Text('Navigate to Next Page'),
),
)
],
),
);
}
Future<void> _reload(var value) async {
print(
'Home Page resumed after popping/closing SecondPage with value {$value}. Do something.');
}
}
second_page.dart
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
Navigator.pop(context, 'Passed Value');
/// It's important that returned value (boolean) is false,
/// otherwise, it will pop the navigator stack twice;
/// since Navigator.pop is already called above ^
return false;
},
child: Scaffold(
appBar: AppBar(),
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(child: Text('Second Page')),
],
),
),
);
}
}
#Rick's answer works like a charm even for Riverpod state management as I coul d not get the firstscreen to rebuild to show changes to the 'used' status of a coupon on the second screen where there is a button to use it.
So I did on the details page:
return WillPopScope(
onWillPop: () async {
//final dumbo = ref.read(userProvider.notifier).initializeUser();
Navigator.pop(context);
return false; //true;
},
Then on the coupons list page, each coupon card has:
child: Card(
elevation: 10,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24.0),
side: BorderSide(
color: coupons[index].isUsed
? Colors.grey
: Colors.blue),
),
child: InkWell(
splashColor: Colors.blue.withAlpha(30),
onTap: () {
//go to Details screen with Coupon instance
//https://api.flutter.dev/flutter/widgets/Navigator/pushNamed.html
Navigator.pushNamed(context, '/details',
arguments: coupons[index].couponId)
.then((_) => _reload());
/*Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => DetailsScreen(
coupon: coupons[index],
),
),
);*/
},
where I used _ since there is no parameter.
Then the function in class CouponScreen is:
void _reload() {
setState(() {});
}
I believe setState(){} is what triggers the rebuild. I apologize for the formatting but it's very difficult to copy and paste Flutter code that is nested deeply.
I am trying to include biometric authentication using local_auth package. This is used when the app starts. The fingerprint is used to determine whether the user is the owner of the phone. If it is confirmed, they will be taken to the home page. The below code works but what I would like to apply on the below code is MVC or design pattern. Can someone guide me?
class LoginOptionState extends State<LoginOption> {
final LocalAuthentication auth = LocalAuthentication();
String _authorized = 'Not Authorized';
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Container(
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Column(
children: <Widget>[
Text("Touch ID"),
SizedBox(height: 10),
GestureDetector(
child: Image.asset(
"assets/",
),
onTap: _authenticate),
],
),
],
),
)));
}
Future<void> _authenticate() async {
bool authenticated = false;
try {
authenticated = await auth.authenticateWithBiometrics(
localizedReason: 'Scan your fingerprint to authenticate',
useErrorDialogs: true,
stickyAuth: false);
} on PlatformException catch (e) {
print(e);
}
if (!mounted) return;
setState(() {
_authorized = authenticated
? Navigator.pushNamed(context, homePageViewRoute)
: 'Not Authorized';
});
}
}
Use the excellent library by Greg Perry mvc_pattern. Quick start sample code and explanation is provided on the link.
Here is a quick start example of the classic counter app, from the link above:
The view:
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
// Fields in a Widget subclass are always marked "final".
static final String title = 'Flutter Demo Home Page';
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final Controller _con = Controller.con;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
widget.title,
),
Text(
'${_con.counter}',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(
_con.incrementCounter
);
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Controller class:
class Controller extends ControllerMVC {
/// Singleton Factory
factory Controller() {
if (_this == null) _this = Controller._();
return _this;
}
static Controller _this;
Controller._();
/// Allow for easy access to 'the Controller' throughout the application.
static Controller get con => _this;
int get counter => _counter;
int _counter = 0;
void incrementCounter() => _counter++;
}
Now the above code refactored, to add a model:
int get counter => Model.counter;
void incrementCounter() {
/// The Controller knows how to 'talk to' the Model. It knows the name, but Model does the work.
Model._incrementCounter();
}
And finally the model class:
class Model {
static int get counter => _counter;
static int _counter = 0;
static int _incrementCounter() => ++_counter;
}
However make sure you upgrade flutter to version 1.13.0. At least for me, I was getting several build errors in lower versions.
Karee is a set of tools that implementes MVC design in Flutter. It help you to manage your Controllers, your routes, your screens and more. Refer to karee github wiki to get documentation.
You Can use Karee . It supports Flutter 2.X.X
To installation run
npm install -g karee
Then karee create
After creating a New Flutter project based on Karee you can add new controller
Sample Code
Creating à New controller
Karee generate --controller --path auth Authentication
Generated file under lib/app/controllers/auth/AuthenticationController
#Controller
class AuthenticationController {
login(username, password) {
/// Your custom code
}
}
Add route
Route.on('/login', 'AuthenticationController#login');
Use in your Screen
var authUser = KareeRouter.goto('/login', parameter:[username, password]);
i'm brand new to Flutter.
I'm trying to open a panel pressing a button and than closing it by pressing a button on that panel.
I've managed to do it by writing the code in the same page.
What i can't do is splitting the code and keep everything working.
What I'm actually doing is calling a variable in the State of a widget that is initialized False and then with an if statement i'm calling: or an empty container or the panel i want.
When i press the button i call SetState(){} and the variable changes to true to let the panel appears, then in the panel there's button that do opposite thing.
Assuming that what i'm doing it is correct. How to i keep doing this with the panel refactored in a new page?
I've red something about streams and inherited widgets but i haven't completely understood
If I understand correctly, you want to notify a StatefullWidget from another StatefullWidget. There are several approaches on this one but since you've mentioned Streams, I'll try to post an example and explain a bit this scenario.
So basically, you can consider the streams like a pipe linked to a faucet in one end and the other end it's added into a cup (the end can be split in multiple ends and put in multiple cups, "broadcast streams").
Now, the cup is the listener (subscriber) and waits for water to drop trough the pipe.
The faucet is the emitter, and it will emit water droplets when the faucet is opened.
The faucet can be opened when the other end is put into a cup, this is a smart faucet with some cool sensors, (the emitter will start emitting events when a subscriber is "detected).
The droplets are actual events that are happening in the the app.
Also you must remember to close the faucet in order to avoid a massive leak from your cup into the kitchen floor. (you must cancel the subscribers when you've done handling events to avoid a leak).
Now for your particular case here's the code snippet that kind of illustrate the above metaphor:
class ThePannel extends StatefulWidget { // this is the cup
final Stream<bool> closeMeStream; // this is the pipe
const ThePannel({Key key, this.closeMeStream}) : super(key: key);
#override
_ThePannelState createState() => _ThePannelState(closeMeStream);
}
class _ThePannelState extends State<ThePannel> {
bool _closeMe = false;
final Stream<bool> closeMeStream;
StreamSubscription _streamSubscription;
_ThePannelState(this.closeMeStream);
#override
void initState() {
super.initState();
_streamSubscription = closeMeStream.listen((shouldClose) { // here we listen for new events coming down the pipe
setState(() {
_closeMe = shouldClose; // we got a new "droplet"
});
});
}
#override
void dispose() {
_streamSubscription.cancel(); // THIS IS QUITE IMPORTANT, we have to close the faucet
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
SomeWidgetHere(shouldClose: _closeMe),
RaisedButton(
onPressed: () {
setState(() {
_closeMe = true;
});
},
)
],
);
}
}
class SomeWidgetThatUseThePreviousOne extends StatefulWidget { // this one is the faucet, it will emit droplets
#override
_SomeWidgetThatUseThePreviousOneState createState() =>
_SomeWidgetThatUseThePreviousOneState();
}
class _SomeWidgetThatUseThePreviousOneState
extends State<SomeWidgetThatUseThePreviousOne> {
final StreamController<bool> thisStreamWillEmitEvents = StreamController(); // this is the end of the pipe linked to the faucet
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
ThePannel(closeMeStream: thisStreamWillEmitEvents.stream), // we send the other end of the pipe to the cup
RaisedButton(
child: Text("THIS SHOULD CLOSE THE PANNEL"),
onPressed: () {
thisStreamWillEmitEvents.add(true); // we will emit one droplet here
},
),
RaisedButton(
child: Text("THIS SHOULD OPEN THE PANNEL"),
onPressed: () {
thisStreamWillEmitEvents.add(false); // we will emit another droplet here
},
)
],
);
}
#override
void dispose() {
thisStreamWillEmitEvents.close(); // close the faucet from this end.
super.dispose();
}
}
I hope that my analogy will help you understand a bit the streams concept.
If you want to open an dialog (instead of what you call a "panel") you can simply give the selected data back when you close the dialog again.
You can find a good tutorial here: https://medium.com/#nils.backe/flutter-alert-dialogs-9b0bb9b01d28
you can navigate and return data from another screen like that :
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Returning Data',
home: HomeScreen(),
));
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Returning Data Demo'),
),
body: Center(child: SelectionButton()),
);
}
}
class SelectionButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
_navigateAndDisplaySelection(context);
},
child: Text('Pick an option, any option!'),
);
}
// A method that launches the SelectionScreen and awaits the result from
// Navigator.pop!
_navigateAndDisplaySelection(BuildContext context) async {
// Navigator.push returns a Future that will complete after we call
// Navigator.pop on the Selection Screen!
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
// After the Selection Screen returns a result, hide any previous snackbars
// and show the new result!
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text("$result")));
}
}
class SelectionScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Pick an option'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// Close the screen and return "Yep!" as the result
Navigator.pop(context, 'Yep!');
},
child: Text('Yep!'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: () {
// Close the screen and return "Nope!" as the result
Navigator.pop(context, 'Nope.');
},
child: Text('Nope.'),
),
)
],
),
),
);
}
}
for more details about navigation:
https://flutter.dev/docs/cookbook/navigation/returning-data