How to pass a drawer with a string inside to next screen - flutter

I have the next block of code where I'm getting the AppVersion using a library and after that I'm passing the AppVersion to a drawer. That drawer I send it to next screen but when I open the drawer on the next screen is showing the AppVersion as NULL. What can be the issue ?
I will provide below the full code source and maybe somebody can help me to figure out where is the bug.
import 'package:flutter/material.dart';
import 'package:package_info/package_info.dart';
void main() {
runApp(FirstPage());
}
class FirstPage extends StatefulWidget {
final String title;
FirstPage({Key key, this.title}) : super(key: key);
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
String packageAppVersion = '';
#override
void initState() {
super.initState();
versionCheck();
}
Future<void> versionCheck() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
setState(() {
packageAppVersion = packageInfo.version;
});
}
Widget buildDrawerForSecondPage(BuildContext context) {
return new Drawer(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: new Column(
children: [
Flexible(
child: new ListView(
children: <Widget>[],
),
),
Flexible(
flex: 0,
child: Text("App version: $packageAppVersion"),
)
],
),
),
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Drawer Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SecondPage(
title: 'Second Page',
drawer: buildDrawerForSecondPage(context),
),
);
}
}
class SecondPage extends StatefulWidget {
final String title;
final Drawer drawer;
SecondPage({Key key, this.title, this.drawer}) : super(key: key);
#override
_SecondPageState createState() => _SecondPageState(drawer);
}
class _SecondPageState extends State<SecondPage> {
String packageAppVersion = '';
final Drawer drawer;
_SecondPageState(this.drawer);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
endDrawer: drawer,
body: Container(),
);
}
}
Thanks in advance.

That's because you are using a .then() syntax, the AppVersion actually gets updated but a bit later hence the null value. You could await the version before the run() method and then pass it down to MaterialApp, or you could try using a setState after the print inside then(). Let me know if this fixes your issue.

Initially, the value of packageAppVersion is null, that is what it is being shown in the UI. So to update the UI you need to use setState. Check the below code for a better understanding:
#override
void initState() {
super.initState();
versionCheck();
}
Future<void> versionCheck() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
setState((){
packageAppVersion = packageInfo.version;
});
print('App version received: $packageAppVersion');
}

I found the fix for above code, but I don't understand why is working only like this (removed the drawer property and the constructor from the _SecondPageState) :
class SecondPage extends StatefulWidget {
final String title;
final Drawer drawer;
SecondPage({Key key, this.title, this.drawer}) : super(key: key);
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
String packageAppVersion = '';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
endDrawer: widget.drawer,
body: Container(),
);
}
}

Related

As a common appbar widget how to change appbar color when page is scrolled Flutter

Good morning friends, I'm trying to make the appbar transparent or white in scrollable
parts.
For me, this solution An efficient way in Flutter to change appbar color when scrolled works, but as the person said, I don't want to use setState continuously and do it in every separate component, so I'm trying to do what is mentioned in the comment. For this reason, I created a common appbar widget so that I can use it in other components. I made the CustomAppBar widget statefull, but I don't know where to add the scrollController. Therefore, I see errors. If anyone has time, can you help?
The code below is the widget where I call CustomAppBar.
import ...
const ExtractionBody({Key? key, required this.goal}) : super(key: key);
final Objective goal;
#override
ExtractionBodyState createState() => ExtractionBodyState();
}
class ExtractionBodyState extends ConsumerState<ExtractionBody> {
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(100),
child: CustomAppBar(
icon: IconButton(
icon: const Icon(PhosphorIcons.xBold),
onPressed: () => Navigator.of(context).pushNamedAndRemoveUntil(
HomePage.routeName,
(route) => false,
),
),
),
),
body: ExtractionRequestContent(
goal: widget.goal, scrollController: _scrollController),
);
}
}
Finally, this is my CustomAppBar code. Thank you very much in advance. and have a good weekend everyone
class CustomAppBar extends StatefulHookConsumerWidget {
static String routeName = "/extraction_body";
const CustomAppBar({Key? key, this.icon})
: preferredSize = const Size.fromWidth(50),
super(key: key);
final Widget? icon;
#override
final Size preferredSize; // default is 56.0
#override
CustomAppBarState createState() => CustomAppBarState();
}
class CustomAppBarState extends ConsumerState<CustomAppBar> {
bool isAppbarCollapsing = false;
final ScrollController _scrollController = ScrollController();
#override
void initState() {
super.initState();
_initializeController();
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void _initializeController() {
_scrollController.addListener(() {
if (_scrollController.offset == 0.0 &&
!_scrollController.position.outOfRange) {
//Fully expanded situation
if (!mounted) return;
setState(() => isAppbarCollapsing = false);
}
if (_scrollController.offset >= 9.0 &&
!_scrollController.position.outOfRange) {
//Collapsing situation
if (!mounted) return;
setState(() => isAppbarCollapsing = true);
}
});
}
#override
Widget build(BuildContext context) {
return AppBar(
elevation: 0,
backgroundColor:
isAppbarCollapsing ? AppColors.monochromeWhite : Colors.transparent,
title: Text(context.l10n.buttonCancel),
titleSpacing: 4,
leading: widget.icon,
);
}
}
Thanks!
Instead of define ScrollController in CustomAppBar, pass it in constructor like this:
class CustomAppBar extends StatefulHookConsumerWidget {
static String routeName = "/extraction_body";
const CustomAppBar({Key? key, this.icon, required this.scrollController})
: preferredSize = const Size.fromWidth(50),
super(key: key);
final Widget? icon;
final ScrollController scrollController;
#override
final Size preferredSize; // default is 56.0
#override
CustomAppBarState createState() => CustomAppBarState();
}
and use it like this:
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
final ScrollController scrollController = ScrollController(); //<---- define scrollController here
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(100),
child: CustomAppbar(scrollController: scrollController)),
body: ListView.builder(
controller: scrollController,
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 100,
width: 100,
color: Colors.red,
margin: EdgeInsets.all(12),
);
},
),
));
}
}

How to TextFormField in flutter when state changes, using Provider/ChangeNotifier?

I have an issue with updating text inside TextFormField when using Provider as state management.
I reduced my problem to an abstract one (I removed all the clutter code) and here how it works:
there is a someValue in AppState
the someValue can be edited via Form->TextFormField
the someValue is to be reflected as a title of the AppBar when typing (onChange)
the someValue can be updated from external source (in the example it is a button that updates it)
when someValue is updated from external source, it MUST be updated in text Form->TextFormField as well
The last one is causing me the problem. Consider the following code:
AppState.dart
import 'package:flutter/foundation.dart';
class AppState extends ChangeNotifier{
String someValue = '';
updateSomeValue(String newValue){
someValue = newValue;
notifyListeners();
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:text_ctrl_issue/app_state.dart';
void main() {
runApp(ChangeNotifierProvider(create: (_) => AppState(), child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late TextEditingController _controller;
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
_controller = TextEditingController();
}
#override
Widget build(BuildContext context) {
final provider = Provider.of<AppState>(context);
// following line of code makes it possible for text to be changed by button
// and reflected in TextFormField
// but it causes nasty side effect, that when typing, cursor always goes to beginning of the line
_controller.text = provider.someValue;
return Scaffold(
appBar: AppBar(
title: Text(provider.someValue),
),
body: Center(
child: Form(
key: _formKey,
child: Column(children: [
TextFormField(
controller: _controller,
onChanged: (value) {
provider.updateSomeValue(value);
},
),
ElevatedButton(
onPressed: () {
provider.updateSomeValue('foo_bar');
},
child: Text('change text external source'))
])),
),
);
}
}
The problem:
When I added the line _controller.text = provider.someValue; it fixed the issue of updating TextFormField when button is clicked, but it create new issue, that when typing in TextFormField, it is also triggered, cause carret of text field to move to the beginning of the text field.
How to make it work so the text (value) of a TextFormField can be updated externally, without causing carret issue when typing?
EDIT
The answer of Yeasin Sheikh using addListener doesn't quite work (it is hacky) because:
it listens to every event (e.g. onFocus or cursor changed)
it does not take into account situation that EleveatedButton is in different scope than _controller (e.g. is in different widget).
An easy way of doing this by listening TextEditingController, while the TextFormField is the ruler here.
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late TextEditingController _controller;
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
_controller = TextEditingController()
..addListener(() {
Provider.of<AppState>(context, listen: false)
.updateSomeValue(_controller.text);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(context.watch<AppState>().someValue),
),
body: Center(
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _controller,
),
ElevatedButton(
onPressed: () {
_controller.text = 'foo_bar';
},
child: Text('change text external source'))
],
),
),
),
);
}
}
Also, you can check riverpod
import 'package:flutter/foundation.dart';
class AppState extends ChangeNotifier
{
TextEditingController _controller=TextEditingController();
TextEditingController get controller=>_controller();
String someValue = '';
updateSomeValue(String newValue)
{
someValue = newValue;
notifyListeners();
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:text_ctrl_issue/app_state.dart';
void main() {
runApp(ChangeNotifierProvider(create: (_) => AppState(), child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
#override
void dispose() {
super.dispose();
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
final provider = Provider.of<AppState>(context);
return Scaffold(
appBar: AppBar(
title: Text(provider.someValue),
),
body: Center(
child: Form(
key: _formKey,
child: Column(children: [
TextFormField(
controller: Provider.controller,
onChanged: (v) {
provider.updateSomeValue(v);
},
),
ElevatedButton(
onPressed: () {
provider.updateSomeValue('foo_bar');
},
child: Text('change text external source'))
])),
),
);
}
}

How to fix hive box already open error in flutter?

I am trying to use hive to store data on a local machine using hive but each time when I compile the code it gives the error "The box "notebook" is already open and of type Box."
Can someone help me to resolve the issue as I am new to it? Thanks
I am just trying to add data to the database in this app without any change to the state of the app interface. I have tried to change the main method to void but no luck on this.
All the code is located in the main file
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'notes.dart';
import 'notesStoring.dart';
Future main() async{
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
Hive.registerAdapter(NotesAdapter());
await Hive.openBox<NotesAdapter>('noteBook');
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
#override
void dispose() {
Hive.close();
// TODO: implement dispose
super.dispose();
}
#override
Future incrementCounter(String title) async {
final notes = Notes()
..title = title;
final box =Boxes.getNotesValues();
box.add(notes);
}
final titleForNotes=TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body:
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
TextField(
controller: titleForNotes,
cursorColor: Colors.pink,
),
ValueListenableBuilder<Box<Notes>>(valueListenable: Boxes.getNotesValues().listenable(), builder: (context,box,_){
final noteBook =box.values.toList().cast<Notes>();
return buildContent(noteBook);
})
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
incrementCounter(titleForNotes.text);
},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class Boxes {
static Box<Notes> getNotesValues()=>Hive.box<Notes>('noteBook');
}
Widget buildContent(List<Notes> noteBook){
return Column(
children: [
Expanded(child:
ListView.builder(
padding: EdgeInsets.all(8),
itemCount: noteBook.length,
itemBuilder: (BuildContext context, int index){
final notes= noteBook[index];
return buildTransaction(context, notes);
}
)
)
],
);
}
Widget buildTransaction(
BuildContext context,
Notes notes,
){
return Card(
color: Colors.green,
child: Text(notes.title),
);
}
1.You can open your notebook Box in the main method of your app:
Future<void> main() async {
...
final appDocumentDirectory = await
path_provider.getApplicationDocumentsDirectory();
Hive.init(appDocumentDirectory.path);
Hive.registerAdapter(UserAdapter());
// open the user box
await Hive.openBox('notebook');
_setUpLogging();
runApp(MultiProvider(providers: providers, child:
StartupApplication()));
}
2 Access the previously opened box like below:
class MyHomePage extends StatefulWidget {
const MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// user box
Box notebookBox;
#override
void initState() {
super.initState();
// get the previously opened user box
notebookBox = Hive.box('notebook');
}
#override
Widget build(BuildContext context) {
// check for your conditions
return (notebookBox.values.isNotEmpty && notebookBox.get(0).active == 1)
? HomeView()
: Intro();
}
}

Fetching data from Supabase in flutter

Anyone can help me with this, I'm trying to fetch data from a table in Supabase but it's showing error on the app screen.
I want to build an app that reads the data from Supabase without any authentication, only reading and displaying the data from the table, here I'm just testing before I start building my app.
a screenshot from the main page in the app
My code in main.dart
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Supabase.initialize(
url: '[https://bougcsiwnimbmnmvwjlb.supabase.co]',
anonKey: '[eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTY0Mzc5NzQyOCwiZXhwIjoxOTU5MzczNDI4fQ.Ac5s-ZOyUV-2rRoP_GUuPvdt7tGNocCSq-LU-ZtBVqQ]',
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
// SuPabase
class _MyHomePageState extends State<MyHomePage> {
Future<void> _getProfile(String name) async {
late final _usernameController ;
#override
Future<Widget> build(BuildContext context) async {
final response = await Supabase.instance.client
.from('channels')
.select()
.single()
.execute();
if (response.error != null && response.status != null) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(response.error!.message)));
}
if (response.data != null) {
_usernameController = response.data!['name'] as String;
}
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
children: [
Text(_usernameController,
),
],
),
);
}
}
#override
Widget build(BuildContext context) {
throw UnimplementedError();
}
}
I really appetite if anyone can help in this.
I think I can spot few spots that you might want to fix.
First, you want to remove the [] around your Supabase URL and Supabase anon key like this.
await Supabase.initialize(
url: 'https://bougcsiwnimbmnmvwjlb.supabase.co',
anonKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTY0Mzc5NzQyOCwiZXhwIjoxOTU5MzczNDI4fQ.Ac5s-ZOyUV-2rRoP_GUuPvdt7tGNocCSq-LU-ZtBVqQ',
);
Also, it seemed like you had some mismatch of brackets in your widget definition. I think this is along the line of what you wanted to do:
class _MyHomePageState extends State<MyHomePage> {
String? name;
Future<void> _getProfile() async {
final response = await Supabase.instance.client
.from('channels')
.select()
.single()
.execute();
if (response.error != null && response.status != null) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(response.error!.message)));
}
if (response.data != null) {
setState(() {
name = response.data!['name'] as String;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: ListView(
padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 12),
children: [
name == null ? const Text('loading') : Text(name!),
],
),
);
}
#override
void initState() {
_getProfile();
super.initState();
}
}

Flutter: setState() called in constructor

I am novice in Flutter, and ran upon a problem trying to show a map and implement some tilelayers.
The app has a Drawer implemtation where I want to enable/disable and clear the tile cache.
I have fetched some examples where this was working well, so I know that the tiling works great, but here i ran upon a problem where I want to call member functions of the MyWorldMap stateful widget from the drawer widget, and to my spare knowledge I now are plagued by the setState() called in constructor error message.
Do you have any suggestions to help, or guide me on the correct path ?
Note !! Remember to add your own MAP API KEY according to: https://codelabs.developers.google.com/codelabs/google-maps-in-flutter?hl=en&continue=https%3A%2F%2Fcodelabs.developers.google.com%2F#3
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget{
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title:"My App test",
theme: ThemeData(primarySwatch: Colors.blue),
home: HomePage(title: "My World Map")
);
}
}
class HomePage extends StatefulWidget{
final String title;
HomePage({Key? key, required this.title}):super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>{
#override
void initState(){
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: MainDrawer(),
body: MyWorldMap(),
);
}
}
class MainDrawer extends StatefulWidget{
#override
State<StatefulWidget> createState() => MainDrawerState();
}
class MainDrawerState extends State<MainDrawer>{
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
const DrawerHeader(
decoration: BoxDecoration(color: Colors.blue),
child: Text("My World Map"),
),
ListTile(
title: const Text ("Add tile overlay"),
onTap: () => addTileOverlay(),
),
ListTile(
title: const Text ("Clear tile overlay cache"),
onTap: () => clearTileCache(),
),
ListTile(
title: const Text ("Remove tile overlay"),
onTap: () => removeTileOverlay(),
),
],
),
);
}
void addTileOverlay(){
print("Attempting to add tile overlay");
MyWorldMap().addTileOverlay();
}
void clearTileCache(){
print("Attempting clear tile cache");
MyWorldMap().clearTileCache();
}
void removeTileOverlay(){
print("Attempting removing tile overlay");
MyWorldMap().removeTileOverlay();
}
}
class MyWorldMap extends StatefulWidget{
const MyWorldMap({Key? key}) : super(key: key);
addTileOverlay() => createState()._addTileOverlay();
removeTileOverlay() => createState()._removeTileOverlay();
clearTileCache() => createState()._clearTileCache();
#override
_MyWorldMapState createState() => _MyWorldMapState();
}
class _MyWorldMapState extends State<MyWorldMap>
{
TileOverlay? _tileOverlay;
late GoogleMapController _mapController;
final LatLng _initialCameraPosition = const LatLng(61.9026,6.7003); //Change with your location
//You need to change maps API key in AndroidManifest.xml
#override
void initState(){
super.initState();
}
Future<void> _onMapCreated(GoogleMapController controller) async {
_mapController = controller;
setState(() {
//Do stuff ?
});
}
#override
Widget build(BuildContext context) {
Set<TileOverlay> overlays = <TileOverlay>{
if(_tileOverlay != null) _tileOverlay!,
};
return GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: CameraPosition(
target: _initialCameraPosition,
zoom:15,
),
myLocationEnabled: false,
tileOverlays: overlays,
);
}
void _addTileOverlay()
{
final TileOverlay tileOverlay = TileOverlay(
tileOverlayId: TileOverlayId("My World Map Overlay"),
tileProvider: MyWorldMapTileProvider(),
);
setState((){ //The code fails here when pushing the 'Add tile overlay button' !!
_tileOverlay = tileOverlay;
});
}
void _clearTileCache()
{
if(_tileOverlay != null){
_mapController.clearTileCache(_tileOverlay!.tileOverlayId);
}
}
void _removeTileOverlay()
{
setState(() {
_tileOverlay = null;
});
}
}
class MyWorldMapTileProvider implements TileProvider {
#override
Future<Tile> getTile(int x, int y, int? zoom) async {
String path = 'https://maptiles1.finncdn.no/tileService/1.0.1/norortho/$zoom/$x/$y.png';
http.Response response = await http.get(
Uri.parse(path)
);
return Tile(x,y,response.bodyBytes);
}
}
Not that I am a real professional with flutter, but I think the problem might reside in here:
addTileOverlay() => createState()._addTileOverlay();
removeTileOverlay() => createState()._removeTileOverlay();
clearTileCache() => createState()._clearTileCache();
You're creating a new state each time you invoke one of those methods in MyWorldMap widget, and I don't think that's the correct behaviour.
If you want to edit a Widget state from another Widget, you should try using keys: I think any stateful Widget can take a key argument in the constructor, that can be used in turn to change its state from other widgets. I'll try writing a simple example.
class Parent extends StatelessWidget {
final keyA = GlobalKey();
final keyB = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: [
ChildA(keyA),
ChildB(keyB, keyA),
]),
);
}
}
class ChildA extends StatefulWidget {
const ChildA(GlobalKey key) : super(key: key);
#override
State<StatefulWidget> createState() => ChildAState();
}
class ChildAState extends State<ChildA> {
int counter = 0;
#override
Widget build(BuildContext context) {
return Text("Child A count: $counter");
}
void increaseCounter(){
setState(() {
counter++;
});
}
}
class ChildB extends StatefulWidget {
final GlobalKey childAKey;
const ChildB(GlobalKey key, this.childAKey) : super(key: key);
#override
State<StatefulWidget> createState() => ChildBState();
}
class ChildBState extends State<ChildB> {
#override
Widget build(BuildContext context) {
return TextButton(
child: const Text("Press here"),
onPressed: () {
(widget.childAKey.currentState as ChildAState).increaseCounter();
},
);
}
}
After #il_boga lead me to the answer (all credits to him), I'll post the working code here:
I moved the TileOverlay creation to initState of _MyWorldMapState class, and added a buffered 'layer' too so I could switch on/off the layer by setting _mapTileOverlay to null when removing and back to _bufferedMapTileOverlay when adding the overlay.
Further I have created two GlobalKeys (actually not knowing why i need drawerKey actually, since I never activily reference it anywhere..., mapKey is obvious)
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget{
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title:"My App test",
theme: ThemeData(primarySwatch: Colors.blue),
home: HomePage(title: "My World Map")
);
}
}
class HomePage extends StatefulWidget{
final String title;
const HomePage({Key? key, required this.title}):super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>{
final drawerKey = GlobalKey();
final mapKey = GlobalKey();
#override
void initState(){
print("_HomePageState(): initState");
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: MainDrawer(drawerKey: drawerKey, mapKey: mapKey,),
body: MyWorldMap(mapKey: mapKey,),
);
}
}
class MainDrawer extends StatefulWidget{
final GlobalKey mapKey;
const MainDrawer({required GlobalKey drawerKey, required this.mapKey}) : super(key: drawerKey);
#override
State<StatefulWidget> createState() => MainDrawerState();
}
class MainDrawerState extends State<MainDrawer>{
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
const DrawerHeader(
decoration: BoxDecoration(color: Colors.blue),
child: Text("My World Map"),
),
ListTile(
title: const Text ("Add tile overlay"),
onTap: () => addTileOverlay(),
),
ListTile(
title: const Text ("Clear tile overlay cache"),
onTap: () => clearTileCache(),
),
ListTile(
title: const Text ("Remove tile overlay"),
onTap: () => removeTileOverlay(),
),
],
),
);
}
void addTileOverlay(){
print("Attempting to add tile overlay");
//MyWorldMap().addTileOverlay();
(widget.mapKey.currentState as _MyWorldMapState)._addTileOverlay();
}
void clearTileCache(){
print("Attempting clear tile cache");
//MyWorldMap().clearTileCache();
(widget.mapKey.currentState as _MyWorldMapState)._clearTileCache();
}
void removeTileOverlay(){
print("Attempting removing tile overlay");
//MyWorldMap().removeTileOverlay();
(widget.mapKey.currentState as _MyWorldMapState)._removeTileOverlay();
}
}
class MyWorldMap extends StatefulWidget{
const MyWorldMap({required GlobalKey mapKey}) : super(key: mapKey);
//addTileOverlay() => createState()._addTileOverlay();
//removeTileOverlay() => createState()._removeTileOverlay();
//clearTileCache() => createState()._clearTileCache();
#override
_MyWorldMapState createState() => _MyWorldMapState();
}
class _MyWorldMapState extends State<MyWorldMap>
{
TileOverlay? _bufferedMapTileOverlay; //intermediate, which actually holds the overlay
TileOverlay? _mapTileOverlay; //value which connects to the map
late GoogleMapController _mapController;
final LatLng _initialCameraPosition = const LatLng(61.9026,6.7003); //Change with your location
//You need to change maps API key in AndroidManifest.xml
#override
void initState(){
print("_MyWordMapState(): initState");
super.initState();
final TileOverlay newMapTileOverlay = TileOverlay( //Inits the tileOverlay
tileOverlayId: const TileOverlayId("My World Map Overlay"),
tileProvider: MyWorldMapTileProvider(),
);
_bufferedMapTileOverlay = newMapTileOverlay;
}
Future<void> _onMapCreated(GoogleMapController controller) async {
_mapController = controller;
setState(() {
//Do stuff ?
});
}
#override
Widget build(BuildContext context) {
Set<TileOverlay> overlays = <TileOverlay>{ //connect a set of overlays (here just one)
if(_mapTileOverlay != null) _mapTileOverlay!,
};
return GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: CameraPosition(
target: _initialCameraPosition,
zoom:15,
),
myLocationEnabled: false,
tileOverlays: overlays, //connect to the set of overlays (I have only one (see above))
);
}
void _addTileOverlay()
{
setState((){
_mapTileOverlay = _bufferedMapTileOverlay;
});
}
void _clearTileCache()
{
if(_mapTileOverlay != null){
print("Clearing tile cache");
_mapController.clearTileCache(_mapTileOverlay!.tileOverlayId);
}
}
void _removeTileOverlay()
{
setState(() {
_mapTileOverlay = null;
});
}
}
class MyWorldMapTileProvider implements TileProvider {
#override
Future<Tile> getTile(int x, int y, int? zoom) async {
String path = 'https://maptiles1.finncdn.no/tileService/1.0.1/norortho/$zoom/$x/$y.png';
http.Response response = await http.get(
Uri.parse(path)
);
return Tile(x,y,response.bodyBytes);
}
}
Seems like you are using setState before build method has finished building the widgets. I would suggest using setState after build has finished, this way :
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
setState(() {
// do stuff;
});
});