Flutter SearchDelegate "BACK" button doesn't execute "close()" - flutter

I have an appBar with a search button, which when pressed returns a custom DataSearch class that extends SearchDelegate.
When I close the search page (from the device's go back button or from the widget), I need to execute a certain function first.
However the function is only executed from the below widget and not when the device's "BACK" button is pressed.
(Image below)
Here's my code:
class DataSearch extends SearchDelegate<String> {
#override
List<Widget> buildActions(BuildContext context) {
// ...
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
function_I_need_to_execute();
close(context, null);
},
);
}
#override
Widget buildResults(BuildContext context) {
// ...
}
#override
Widget buildSuggestions(BuildContext context) {
// ...
}
}
I've tried this, but no changes:
#override
void close(BuildContext context, String result) {
super.close(context, result);
function_I_need_to_execute();
}
I'm considering using the WillPopScope widget but I'm not sure where it fits in the delegate.
Image:

Seems this is not possible using the close method.
However you can do this using the .then method on showSearch since showSearch is a Future.
The then method registers callbacks to be called when this future completes.
showSearch(context: context, delegate: SearchDelegate()).then((value) async{
await doSomething();
});

Related

SearchDelegate don't return for the page in release mode

I have this code in SearchDelegate
class CustomSearchDelegate extends SearchDelegate<String> {
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: (){
query = "";
},
)
];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: Icon(Icons.arrow_back),
onPressed: (){
close(context, "");
},
);
}
#override
Widget buildResults(BuildContext context) {
close(context, query );
return Container();
}
#override
Widget buildSuggestions(BuildContext context) {
return Container();
}
}
When I run it in debug mode, it works properly. But when I run in release mode, when confirming the search, it doesn't return to the page that called Search, it just returns container, even with the "close(context, query );" before in buildResults.
I couldn't solve this specific problem, I really searched for any possible solution, but it seems to have to do with the way flutter generates the apk in release. In my case I just gave up using showSearch with SearchDelegate, and created a custom Appbar like a SearchBar. I'll leave below some links that I based it on
This was the one I used the most as an example
https://github.com/ahmed-alzahrani/Flutter_Search_Example
Custom AppBar Flutter
This option I haven't used but I found it interesting as another option to optimize your SearchBar
https://pub.dev/packages/flutter_search_bar

Flutter :how to call function in statelesswidget?

I want to show a list of data with list view and JSON. This code in a file with stateless widget. And this page opened after login. When I try in stateful widget, the code RUN normally. In stateless widget, when I debug it, the code didn't call function getData(). But directly went to
Widget build(BuildContext context) {
return new Scaffold( ...
Here is complete code :
class frmClass extends StatelessWidget{
List<dynamic> dta;
get setState => null;
Future<String> getData() async {
var uri ='https://xxxx.com/class';
var map = new Map<String, dynamic>();
map["username"] = "abc";
map["password"] = "1234";
http.Response response = await http.post(
uri,
body: jsonEncode(map),
);
Map<String,dynamic> mp = jsonDecode(utf8.decode(response.bodyBytes));
this.setState(() {
dta = mp["data"];
debugPrint(dta.toString());
});
}
#override
void initState(){
this.getData();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
backgroundColor:Colors.transparent,
elevation: 0.0,
iconTheme: new IconThemeData(color: Color(0xFF18D191))),
body: new ListView.builder(
itemCount: dta == null ? 0 : dta.length,
itemBuilder: (BuildContext context, int index){
return new Card(
child: new Text(dta[index]["className"]),
);
}
),
);
}
}
How can I fix it?
You can use a FutureBuilder to call getData() into build() method of StatelessWidget:
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
But, this will fire getData() every time your statelessWidget is rebuild.
Another way is to use reactive programing architecture (like Bloc, rxdart, etc..).
Depends on what do you want, fire getData() every time or just once (or when your conditions are true/false).
This is happening because stateless widgets don't have initState that's why the below code will never get called. Stateless widgets are more sort of rendering flat UI with no lifecycle methods if you want to use Stateless widget then pass data in the class constructor and use it where ever you want
#override
void initState(){
this.getData();
}
if you want to call it from a stateless widget? Well, that’s possible too. Use a stateful widget as a your root widget that you can provide a callback function too to execute your startup logic. See example below.
Create a StatefulWrapper that takes in a function to call and a child to display.
/// Wrapper for stateful functionality to provide onInit calls in stateles widget
class StatefulWrapper extends StatefulWidget {
final Function onInit;
final Widget child;
const StatefulWrapper({#required this.onInit, #required this.child});
#override
_StatefulWrapperState createState() => _StatefulWrapperState();
}
class _StatefulWrapperState extends State<StatefulWrapper> {
#override
void initState() {
if(widget.onInit != null) {
widget.onInit();
}
super.initState();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
Wrap your stateles widget’s UI in a StatefulWrapper and pass on the onInit logic you would like to run
class StartupCaller extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StatefulWrapper(
onInit: () {
_getThingsOnStartup().then((value) {
print('Async done');
});
},
child: Container(),
);
}
Future _getThingsOnStartup() async {
await Future.delayed(Duration(seconds: 2));
}
}
and that’s it. The function will only be called once when the stateful widget is initialized.
This solution from fluttercampus solved my problem , where I wanted to call some method before build inside stateless widget
https://www.fluttercampus.com/guide/230/setstate-or-markneedsbuild-called-during-build/
#override
Widget build(BuildContext context) {
Future.delayed(Duration.zero,(){
CheckIfAlreadyLoggedIn();
});
return Container();
}

How to call this.close in buildResult method when using Search Delegate (Flutter)?

I have a very simple SearchApp that has an AppBar with a search Icon and a text widget. When the search Icon is tapped, showSearch is called and the CustomSearchDelegate is called. How do I make it so that in the buildResults method, this.close is immediately called without returning a widget and the query is passed to the SearchApp stateless widget to be displayed on the Text widget.
Here is the code:
class SearchApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
tooltip: 'Search',
icon: const Icon(Icons.search),
//Don't block the main thread
onPressed: () {
showSearch(context: context, delegate: CustomSearchDelegate());
},
),
],
),
body: Text(query), // The query data should be displayed here
);
}
}
class CustomSearchDelegate extends SearchDelegate {
#override
List<Widget> buildActions(BuildContext context) {
return <Widget>[Icon(Icons.close)];
}
#override
Widget buildLeading(BuildContext context) {
return Icon(Icons.arrow_back_ios);
}
#override
Widget buildResults(BuildContext context) {
// GO back to SearchApp page immediately with the query result
}
#override
Widget buildSuggestions(BuildContext context) {
return Column();
}
}
I realized I could edit the source code of the search class, so I copied and pasted the source code and under the Textfield, I simply deleted the showResult method and replaced it with close, so no results page would be shown. As for passing the query to the original page, all I had to do was store the value in an asynchronous variable.
If you just want to use close() function without buildResults() you can override showResults()
#override
void showResults(BuildContext context) {
// TODO: implement showResults
super.showResults(context);
}
to this:
#override
void showResults(BuildContext context) {
close(context, query);
}
like this:
class CustomSearchDelegate extends SearchDelegate<String> {
#override
List<Widget> buildActions(BuildContext context) {
return <Widget>[Icon(Icons.close)];
}
#override
Widget buildLeading(BuildContext context) {
return Icon(Icons.arrow_back_ios);
}
#override
Widget buildResults(BuildContext context) {
// unused
return const SizedBox();
}
#override
void showResults(BuildContext context) {
close(context, query);
}
#override
Widget buildSuggestions(BuildContext context) {
return Column();
}
}
call like this to get query result:
onPressed: () async {
String? result = await showSearch(context: context, delegate: CustomSearchDelegate());
}

Trigger snackbar message on page load in Flutter

I am new to Flutter and trying to trigger a snack bar on page load if a message was returned from the page I navigated from. I have managed to get the message to display on a button click, but get an error stating that my context does not have a Scaffold if I try to do it elsewhere.
I am also struggling to find an example of how to show a sack bar without user interaction, so if anyone has a reference, that would surely go a long way in helping as well.
Here is a simplified version of my view:
class LandingView extends StatefulWidget {
final LandingViewModel viewModel;
LandingView(this.viewModel);
#override
State<StatefulWidget> createState() {
return new _ViewState();
}
}
class _ViewState extends State<LandingView> {
#override
void initState() {
super.initState();
}
void _showSnackbar(context, message) {
final snackBar = SnackBar(
content: Text(message),
);
Scaffold.of(context).showSnackBar(snackBar);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: new GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(new FocusNode());
},
child: _buildLayout(context),
),
),
);
}
Widget _buildLayout(BuildContext context) {
Map<String, dynamic> args = getArgs(context); //get value from previous page
if (args != null &&
args["Toast Message"] != null) //check if a value was returned from the previous page. This has been tested and a valid string is being returned
_showSnackbar(
context, args["Toast Message"]); //if so call snack bar function
// this throws an error saying "Scaffold.of() called with a context that does not contain a Scaffold"
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints boxConstraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: boxConstraints.maxHeight),
child: RaisedButton(
child: Text(
"Show Snack Bar",
),
onPressed:
() {
if (args != null &&
args["Toast Message"] !=
null) //check if a value was returned from the previous page. This has been tested and a valid string is being returned
_showSnackbar(context,
args["Toast Message"]); //if so call snack bar function
//this works perfectly
}),
),
);
});
}
}
Any advice would be greatly appreciated
You're getting that because your LandingView widget is not in a Scaffold. You can fix this by putting the LandingView widget inside a StatelessWidget with a Scaffold and changing any references to LandingView to LandingViewPage:
class LandingViewPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: LandingView()
);
}
}
We can do this with addPostFrameCallback method
#override
void initState(){
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) => scaffold.showSnackBar(SnackBar(content: Text("snackbar")));
}
In a stateful widget put:
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Error")));
});
}

How to get context in the any function of StatelessWidget?

We want to show an AlertDialog after some asynchronous processing such as network processes.
When calling 'showAlertDialog ()' from an external class, I want to call it without context. Is there a good way?
class SplashPage extends StatelessWidget implements SplashView {
BuildContext _context;
#override
Widget build(BuildContext context) {
this._context = context;
...
}
I've considered the above method, but I'm worried about side issues.
Help
My current code
class SplashPage extends StatelessWidget implements SplashView {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: MyStoreColors.eats_white1_ffffff,
body: Center(
child: new SvgPicture.asset('assets/ic_splash.svg'),
),
);
}
#override
void showAlertDialog() {
showDialog<void>(
context: /*How to get context?*/,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Not in stock'),
content: const Text('This item is no longer available'),
actions: <Widget>[
FlatButton(
child: Text('Ok'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
#override
void moveToHomeContainer() {
}
#override
void moveToLoginContainer() {
}
}
To show an AlertDialog you need the context, but in StatelessWidget you do not have access to it directly as in StatefulWidget.
Few options are [1]:
passing it as GlobalKey [2]
passing build context as parameter to any other function inside StatelessWidget
use a service to inject the dialog without context [3]
Cheers.
You should trigger rebuild when the async event complete, either convert your widget to StatefulWidget and call setState() or use a state management solution like Bloc.
For example using StatefulWidget your code will look like this:
class SplashPage extends StatefulWidget {
#override
State<SplashPage> createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> implements SplashView {
bool _asynOpDone = false;
/// Call this when the async operation is done.
void _onAsynOpDone() => setState(() => _asyncOpDone = true);
#override
Widget build(BuildContext context) {
if (_asyncOpDone) showAlertDialog(context);
return Scaffold(
...,
///
);
}
#override
void showAlertDialog(BuildContext context) {
showDialog<void>(
context: context,
builder: ...,
);
}
}
You can apply Builder pattern concept to simplify this.
There is a little example here.
button_builder.dart