How can i use show case view in flutter? - flutter

I use showCaseView package in my app, and want to showcase for one time (just after the first start),
How can I do this only once and not show it on the next launches?
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(
(_) {
ShowCaseWidget.of(myContext).startShowCase([_one]);
}
);
}
#override
Widget build(BuildContext context) {
return ShowCaseWidget(
// onFinish: ,
builder:
Builder(builder: (context) {
myContext = context;
return Scaffold(
floatingActionButton: Showcase(
key: _one,
title: 'Title',
description: 'Desc',
child: InkWell(
onTap: () {},
child: FloatingActionButton(
onPressed: (){
print("floating");
}
)
),
),
);
}));
}

You can easily do this with the shared_preferences package:
class IsFirstLaunchPage extends StatefulWidget {
static const PREFERENCES_IS_FIRST_LAUNCH_STRING = "PREFERENCES_IS_FIRST_LAUNCH_STRING";
#override
_IsFirstLaunchPageState createState() => _IsFirstLaunchPageState();
}
class _IsFirstLaunchPageState extends State<IsFirstLaunchPage> {
GlobalKey _one = GlobalKey();
BuildContext myContext;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(
(_) {
_isFirstLaunch().then((result){
if(result)
ShowCaseWidget.of(myContext).startShowCase([_one]);
});
}
);
}
#override
Widget build(BuildContext context) {
return ShowCaseWidget(
// onFinish: ,
builder:
Builder(builder: (context) {
myContext = context;
return Scaffold(
floatingActionButton: Showcase(
key: _one,
title: 'Title',
description: 'Desc',
child: InkWell(
onTap: () {},
child: FloatingActionButton(
onPressed: () {
print("floating");
}
)
),
),
);
}));
}
Future<bool> _isFirstLaunch() async{
final sharedPreferences = await SharedPreferences.getInstance();
bool isFirstLaunch = sharedPreferences.getBool(IsFirstLaunchPage.PREFERENCES_IS_FIRST_LAUNCH_STRING) ?? true;
if(isFirstLaunch)
sharedPreferences.setBool(IsFirstLaunchPage.PREFERENCES_IS_FIRST_LAUNCH_STRING, false);
return isFirstLaunch;
}
}

Related

setState() not updating UI elements even though the state variable, a Future, is updated?

I have a HomePage screen which has a FutureBuilder List implemented with a Future function as the state variable. I am updating this Future in another dart file by using keys to access the future. The Future gets updated and I'm sure of this as I've seen the print statements, but when I call the setState method, the UI doesn't show the newly added entry.
Here's my HomePage.dart:
class HomePage extends StatefulWidget {
const HomePage({super.key});
#override
State<HomePage> createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
Future<List<Model>> getData() async {
return await DatabaseHelper.instance.getModels();
}
Future? userFuture;
#override
void initState() {
super.initState();
userFuture = getData();
print(userFuture);
}
#override
Widget build(BuildContext context) {
print('Building listview');
return Center(
child: FutureBuilder<List<Model>>(
future: userFuture as Future<List<Model>>,
builder: ((context, AsyncSnapshot<List<Model>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const CircularProgressIndicator();
default:
if (snapshot.data!.isEmpty) {
return Text('No data present');
} else if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data?.length,
itemBuilder: ((context, index) {
return MyCard(
key: ValueKey(snapshot.data![index].id),
snapshot.data![index].id,
snapshot.data![index].title,
snapshot.data![index].purpose);
}),
);
}
return Text('data');
}
}),
),
);
}
}
Here's my other dart file. Under the AddEntryState I'm updating the Future state variable and then right after calling the setState method.
class RootPage extends StatefulWidget {
const RootPage({super.key});
#override
State<RootPage> createState() => RootPageState();
}
class RootPageState extends State<RootPage> {
static final GlobalKey<HomePageState> homepageKey =
GlobalKey<HomePageState>();
int currentPage = 0;
List<Widget>? pages;
#override
void initState() {
super.initState();
pages = [
HomePage(key: homepageKey),
StatsPage(),
];
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('App Title'),
),
body: pages?[currentPage],
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => AddEntry()));
},
child: Icon(Icons.add),
),
bottomNavigationBar: NavigationBar(
destinations: [
NavigationDestination(icon: Icon(Icons.home), label: 'Home'),
NavigationDestination(icon: Icon(Icons.data_usage), label: 'Stats'),
],
onDestinationSelected: (int index) {
setState(() {
currentPage = index;
print(index);
});
},
selectedIndex: currentPage,
),
);
}
}
class AddEntry extends StatefulWidget {
const AddEntry({super.key});
#override
State<AddEntry> createState() => _AddEntryState();
}
class _AddEntryState extends State<AddEntry> {
final GlobalKey<FormState> _key = GlobalKey<FormState>();
Map<String, String?> formField = <String, String?>{};
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('New Entry'),
),
body: Form(
key: _key,
child: Column(
children: [
Flexible(
child: MyTextField('Title', callback),
),
Flexible(
child: MyTextField('Purpose', callback),
),
Flexible(
child: MyTextField('Password', callback, obscure: true),
),
TextButton(
onPressed: () async {
if (_key.currentState!.validate()) {
_key.currentState?.save();
formField.forEach((label, value) => print('$label = $value'));
await DatabaseHelper.instance.insertModel(Model(
id: null,
title: formField['Title'],
purpose: formField['Purpose'],
lastAccess: DateTime.now().toString(),
dateAdded: DateTime.now().toString(),
password: formField['Password']));
print(await DatabaseHelper.instance.getModels());
// await DatabaseHelper.instance.deleteAllData();
// print(await DatabaseHelper.instance.getModels());
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Data Saved!'),
action: SnackBarAction(
label: 'Edit',
onPressed: () {
print('edit pressed!');
},
),
),
);
Navigator.pop(context);
print("HomePage userFuture: ");
print(RootPageState.homepageKey.currentState!.userFuture!
.then((result) => print(result)));
print("getData function: ");
print(RootPageState.homepageKey.currentState!
.getData()
.then((result) => print(result)));
print("New Future: ");
print(RootPageState.homepageKey.currentState!.userFuture!
.then((result) => print(result)));
setState(() {
RootPageState.homepageKey.currentState!.userFuture =
RootPageState.homepageKey.currentState!.getData();
});
//add logic to rebuild home screen after every addition of entry
}
},
child: Text('Submit'),
),
],
),
),
);
}
callback(varLabel, varValue) {
formField[varLabel] = varValue;
}
}

showSearch with API

I am trying to implement the search feature and want to get the results from the API.
Under the method buildResults() you will find my comment // data is null but the problem is that I am getting data from the API call. Am I missing something here?
Under buildsResults() I am calling the Future _getResults and returning the received data. I logged the data which you can see.
class SearchBar extends StatefulWidget {
#override
_SearchBarState createState() => new _SearchBarState();
}
class _SearchBarState extends State<SearchBar> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
iconTheme: new IconThemeData(color: Theme.of(context).hintColor),
elevation: 1,
backgroundColor: Theme.of(context).primaryColor,
actions: <Widget>[
IconButton(
autofocus: true,
icon: Icon(Icons.search),
onPressed: () async {
final results = await showSearch<SearchModel>(context: context, delegate: DataSearch(context));
})
],
centerTitle: true,
title: Text('Search content'),
),
);
}
}
class DataSearch extends SearchDelegate<SearchModel> {
final BuildContext parentContext;
final Logger logger = new Logger();
DataSearch(this.parentContext);
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = "";
},
)
];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () {
Navigator.pop(context);
Navigator.pop(parentContext);
},
);
}
#override
Widget buildResults(BuildContext context) {
return FutureBuilder<List<SearchModel>>(
future: _getResults(),
builder: (context, AsyncSnapshot<List<SearchModel>> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
logger.d(snapshot.hasData);
return ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data[index].title),
onTap: () {
close(context, snapshot.data[index]);
},
);
},
itemCount: snapshot.data.length, // data is null
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
);
}
#override
Widget buildSuggestions(BuildContext context) {
return Container();
}
Future<List<SearchModel>> _getResults() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String language = prefs.getString('language');
var data;
await http.get(Constants.BASE_URL + "/search/" + language + "/" + query,).then((response) {
data = convert.jsonDecode(response.body) as List;
});
logger.d(data);
return data.map((model) => SearchModel.fromJson(model)).toList();
}
}
I think that's how it works:
onTap: () async {
final results = await showSearch(context: context, delegate: SearchBar(),query:query);
}
Result gets the return value
Query is the argument passed

Flutter setState public var to another page?

how to setState public var to another page?
int x = 1;
that was in public
in the first page text(x) i want to setstate from the other page
my first page is
class AddFullRequest extends StatefulWidget {
#override
_AddFullRequestState createState() => _AddFullRequestState();
}
class _AddFullRequestState extends State<AddFullRequest> {
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Text(x),
GestureDetector(
onTap: (){
Navigator.of(context).push(new MaterialPageRoute(
builder: (BuildContext context) => AddItemScr()));
},
child: Text('goto'),
),
],
),
);
}
in the other page button to ++ the var in the first page
my other page is
class AddItemScr extends StatefulWidget {
#override
_AddItemScrState createState() => _AddItemScrState();
}
class _AddItemScrState extends State<AddItemScr> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: WillPopScope(onWillPop: (){
Navigator.of(context).pop();
},
child: Column(
children: <Widget>[
FlatButton(onPressed: (){setState(() {
x++;
});}, child: Text('pluss'),)
],
),
),
);
}
}
please help me with this
You can use the callback pattern. In this example, a function (onPressed) is passed to the child. The child calls the function when a button is pressed:
class AddFullRequest extends StatefulWidget {
#override
_AddFullRequestState createState() => _AddFullRequestState();
}
class _AddFullRequestState extends State<AddFullRequest> {
int _x = 0;
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Text("$_x"),
GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => AddItemScr(
onPressed: () => setState(() => _x++),
),
),
);
},
child: Text('goto'),
),
],
),
);
}
}
class AddItemScr extends StatelessWidget {
final VoidCallback onPressed;
const AddItemScr({
Key key,
#required this.onPressed,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
FlatButton(
onPressed: onPressed,
child: Text('Increment'),
),
],
),
);
}
}
You can pass variables between screens. NavigatorState#pop supports passing objects that you can await in the previous screen and set it to it's value.
class AddFullRequest extends StatefulWidget {
#override
_AddFullRequestState createState() => _AddFullRequestState();
}
class _AddFullRequestState extends State<AddFullRequest> {
int x = 0;
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Text('$x'),
GestureDetector(
onTap: () async {
final result = await Navigator.of(context).push<int>(
MaterialPageRoute(
builder: (_) => AddItemScr(variable: x),
),
);
x = result;
setState(() {});
},
child: Text('goto'),
),
],
),
);
}
}
class AddItemScr extends StatefulWidget {
final int variable;
AddItemScr({this.variable});
#override
_AddItemScrState createState() => _AddItemScrState();
}
class _AddItemScrState extends State<AddItemScr> {
int _variable;
#override
void initState() {
_variable = widget.variable;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
FlatButton(
onPressed: () {
setState(() {
_variable++;
});
},
child: Text('pluss'),
),
FlatButton(
onPressed: () {
Navigator.of(context).pop(_variable);
},
child: Text('go back'),
),
],
),
);
}
}

Navigator.of(context).pop() Give me black screen

Noob here.
I've made a update checker with flutter, but if I choose any button, it give me black screen.
How can I fix this? Any ideas?
Code
Full Source : https://github.com/aroxu/LiteCalculator
Dialog Part Source :
import 'package:LiteCalculator/updater/bean/UpdaterBean.dart';
import 'package:flutter/material.dart';
class UpdateHolder extends StatelessWidget {
final List<Version> version;
UpdateHolder({Key key, this.version}) : super(key: key);
#override
Widget build(BuildContext context) {
return calculateResult(
version[0].latestVersion, version[1].currentVersion, context);
}
Widget calculateResult(latestVersion, currentVersion, context) {
print('Latest Version : ${int.parse(latestVersion)}');
print('Current Version : ${int.parse(currentVersion)}');
Widget data;
if ((int.parse(currentVersion) <= int.parse(latestVersion))) {
data = Center(
child: createAlert('Update Required', actions: <Widget>[
FlatButton(
child: Text('OK'),
onPressed: () {
print('OK Button Pressed.');
Navigator.of(context).pop();
},
),
FlatButton(
child: Text('Later'),
onPressed: () {
print('Later Button Pressed.');
Navigator.of(context).pop();
},
),
]),
);
} else
data = Center();
return data;
}
Widget createAlert(content, {List<Widget> actions, title}) {
AlertDialog snackBar;
snackBar = AlertDialog(
content: Text(content),
actions: actions,
);
return snackBar;
}
}
call this for your popup,
void showDialogPopup(){
showDialog(
context: context,
builder: (_)=>AlertDialog(
backgroundColor: Colors.transparent,
content: Container(
child: Center(
child: FlatButton(
onPressed: (){
Navigator.of(context).pop(null);
},
child: Center(
child: Text("close")
)
)
)
)
)
);
}
A black screen or a blank screen? If its a black screen, your are not wrapping your main widget (which goes in the runApp) with a MaterialApp.
You can refer this.
I used url_launcher 5.4.1 to open PlayStore web.
import "package:flutter/material.dart";
import 'package:url_launcher/url_launcher.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: FirstPage(),
debugShowCheckedModeBanner: false,
);
}
}
class FirstPage extends StatefulWidget {
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
#override
void initState() {
_checkUpdate();
super.initState();
}
Future<void> _checkUpdate() async {
await Future.delayed(Duration.zero);
await showDialog(
context: context,
builder: (context) => UpdateDialog(),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("First Page"),
),
);
}
}
class UpdateDialog extends StatefulWidget {
#override
_UpdateDialogState createState() => _UpdateDialogState();
}
class _UpdateDialogState extends State<UpdateDialog> {
Future<void> _updateFound;
#override
void initState() {
_updateFound = _checkForUpdate();
super.initState();
}
Future<bool> _checkForUpdate() async {
await Future.delayed(Duration.zero);
bool updateFound = false;
await Future.delayed(Duration(seconds: 3)); // Do Get call to server
updateFound = true;
if (!updateFound) Navigator.pop(context);
return updateFound;
}
Future<void> _openWebPage() async {
Navigator.pop(context);
launch("https://play.google.com"); //Your link `url_launcher` package
}
void _laterClicked(){
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _updateFound,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CircularProgressIndicator(),
const SizedBox(height: 12.0),
Text("Checking for Update"),
],
),
);
else if (snapshot.hasError)
return AlertDialog(
title: Text("Error Occured"),
content: Text("ERROR: ${snapshot.error}"),
);
else if(snapshot.data)
return AlertDialog(
title: Text("Update Required"),
content: Text(
"Latest version found. Need an update. bla bla bla bla bla bla"),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: _openWebPage,
),
FlatButton(
child: Text("LATER"),
onPressed: _laterClicked,
),
],
);
else
return const SizedBox();
},
);
}
}
This happens whenever you try to pop using the Widget's context.
In the following code:
FlatButton(
child: Text('OK'),
onPressed: () {
print('OK Button Pressed.');
Navigator.of(context).pop();
},
)
context represents the context of the widget, itself (provided in the build method).
To resolve this issue instead of creating a Dialog widget and returning it as the main widget, just use showDialog and return a simple Container().
Use dialogContext to pop the dialog and not the widget itself.
for example:
if ((int.parse(currentVersion) <= int.parse(latestVersion))) {
showDialog(
builder: (dialogContext) => AlertDialog(
content: Text('Update Required'),
actions: <Widget>[
FlatButton(
child: Text('OK'),
onPressed: () {
print('OK Button Pressed.');
Navigator.of(dialogContext).pop();
},
),
FlatButton(
child: Text('Later'),
onPressed: () {
print('Later Button Pressed.');
Navigator.of(dialogContext).pop();
},
),
],
),
);
}
return Container();
I had a similar problem and my solution was something like that:
bool hasBeenShown = false;
if(!hasBeenShown) {
Navigator.pop(context);
}
hasBeenShown = true;
The problem for me was that for some reason Navigator.pop been invoked multiple times when it's supposed to be invoked only once.

How to update state of a ModalBottomSheet in Flutter?

This code is very simple: shows a modal bottom sheet and when the uses clicks the button, it increases the height of the sheet by 10.
But nothing happens. Actually, it only updates its size if the user "slides" the bottom sheet with it's finger (I belive that swipe causes a internal setState on the sheet).
My question is: how do I call the update state of a ModalBottomSheet?
showModalBottomSheet(
context: context,
builder: (context) {
return Container(
height: heightOfModalBottomSheet,
child: RaisedButton(
onPressed: () {
setState(() {
heightOfModalBottomSheet += 10;
});
}),
);
});
You can use Flutter's StatefulBuilder to wrap your ModalBottomSheet as follows:
showModalBottomSheet(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState /*You can rename this!*/) {
return Container(
height: heightOfModalBottomSheet,
child: RaisedButton(onPressed: () {
setState(() {
heightOfModalBottomSheet += 10;
});
}),
);
});
});
Please note that the new setState will override your main widget setState but sure you can just rename it so you would be able to set state of your parent widget and the modal's
//This sets modal state
setModalState(() {
heightOfModalBottomSheet += 10;
});
//This sets parent widget state
setState(() {
heightOfModalBottomSheet += 10;
});
You can maybe use the showBottomSheet from the ScaffoldState. read more here about this showBottomSheet.
This will show the bottomSheet and return a controller PersistentBottomSheetController. with this controller you can call controller.SetState((){}) which will re-render the bottomSheet.
Here is an example
PersistentBottomSheetController _controller; // <------ Instance variable
final _scaffoldKey = GlobalKey<ScaffoldState>(); // <---- Another instance variable
.
.
.
void _incrementBottomSheet(){
_controller.setState(
(){
heightOfModalBottomSheet += 10;
}
)
}
.
void _createBottomSheet() async{
_controller = await _scaffoldKey.currentState.showBottomSheet(
context: context,
builder: (context) {
return Container(
height: heightOfModalBottomSheet,
child: RaisedButton(
onPressed: () {
_incrementBottomSheet()
}),
);
});
}
Screenshot:
Create a class:
class MyBottomSheet extends StatefulWidget {
#override
_MyBottomSheetState createState() => _MyBottomSheetState();
}
class _MyBottomSheetState extends State<MyBottomSheet> {
bool _flag = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
FlutterLogo(
size: 300,
style: FlutterLogoStyle.stacked,
textColor: _flag ? Colors.black : Colors.red,
),
RaisedButton(
onPressed: () => setState(() => _flag = !_flag),
child: Text('Change Color'),
)
],
);
}
}
Usage:
showModalBottomSheet(
context: context,
builder: (_) => MyBottomSheet(),
);
Please refer to the below working code. I created a new Stateful widget(ModalBottomSheet) for the showModalBottomSheet. On button press, we are rebuilding the ModalBottomSheet only which is much cleaner now. We can use AnimationController if need animation for changing the height.
import 'dart:async';
import 'package:flutter/material.dart';
class ModalBottomSheet extends StatefulWidget {
_ModalBottomSheetState createState() => _ModalBottomSheetState();
}
class _ModalBottomSheetState extends State<ModalBottomSheet>
with SingleTickerProviderStateMixin {
var heightOfModalBottomSheet = 100.0;
Widget build(BuildContext context) {
return Container(
height: heightOfModalBottomSheet,
child: RaisedButton(
child: Text("Press"),
onPressed: () {
heightOfModalBottomSheet += 100;
setState(() {});
}),
);
}
}
class MyHomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
Future(() => showModalBottomSheet(
context: context,
builder: (context) {
return ModalBottomSheet();
}));
return new Scaffold(
appBar: new AppBar(
title: new Text("Modal example"),
),
);
}
}
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(title: 'Flutter Demo', home: new MyHomePage());
}
}
create a separate StatefulWidget for the showModalBottomSheet(), like
showModalBottomSheet(
context: context,
builder: (ctx) {
return MapBottomSheet();
});
Bottom Sheet Statefulwidget
class MapBottomSheet extends StatefulWidget {
#override
_MapBottomSheetState createState() => _MapBottomSheetState();
}
class _MapBottomSheetState extends State<MapBottomSheet> {
List<String> places = [];
void _setPlaces(String place) {
setState(() {
places.add(place);
});
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.black12,
child: Column(
children: [
AppTextField(
hint: "Search",
onEditingComplete: () {},
onChanged: (String text) {},
onSubmitted: (String text) async {
// Await the http get response, then decode the json-formatted response.
var response = await http.get(Uri.parse(
'https://api.mapbox.com/geocoding/v5/mapbox.places/$text.json?access_token=pk.eyJ1IjoidjNyc2lvbjkiLCJhIjoiY2ttNnZldmk1MHM2ODJxanh1ZHZqa2I3ZCJ9.e8pZsg87rHx9FSM0pDDtlA&country=PK&fuzzyMatch=false&place=park'));
if (response.statusCode == 200) {
Map<String, dynamic> data = jsonDecode(response.body);
print(data.toString());
List<dynamic> features = data['features'];
features.forEach((dynamic feature) {
setState(() {
_setPlaces(feature['place_name']);
});
});
} else {
print('Request failed with status: ${response.statusCode}.');
}
},
),
Expanded(
child: Container(
height: 250.0,
width: double.infinity,
child: ListView.builder(
itemCount: places.length,
itemBuilder: (ctx, idx) {
return Container(
child: Text(places[idx]),
);
}),
),
),
],
),
);
}
}