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();
}
}
Related
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'))
])),
),
);
}
}
Hello I have simple script I download data from from Rest api , there is my controller
and when I submit the button I Clear the list inside object Employee, after click I call the deleteEmployee() (int the future CRUD operations per row inside table,) , data are cleared, but data inside widget they are not refreshed. and I would like somethin more with that I need to show in table and make same operation per row.
I cant figure where is a problem.
after click the button must look like
class EmployeeController extends GetxController {
var empData = <Employe>[].obs;
#override
void onInit() {
fetchEmployee();
super.onInit();
}
void fetchEmployee() async {
final response =
await http.get(
Uri.parse('http://dummy.restapiexample.com/api/v1/employees'));
if (response.statusCode == 200) {
final string = response.body;
final parsed = json.decode(string);
Employe emp = Employe.fromJson(parsed);
Employe empx = Employe( message: "sampleTest", status: '123', data: emp.data.getRange(0, 4).toList() );
List<Employe> e = [];
e.add(empx);
e.add(emp);
empData.value = e;
} else {
throw Exception('Failed to load emplo');
}
}
void deleteEmployee (String enumId){
debugPrint(empData.toString());
debugPrint("before");
int index = empData.indexWhere((ele) => ele.message == enumId);
empData[index].data.clear();
debugPrint("after");
debugPrint(empData.toString());
}
}
void main() {
runApp(const MyApp());
}
final EmployeeController employeeController = Get.put(EmployeeController());
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> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Obx(() => Row(
children: employeeController.empData
.map(
(e) => Expanded(
child: Column(
children: [
Text(e.message),
Text(e.toString()),
IconButton(
onPressed: () {
employeeController.deleteEmployee(e.message);
},
icon: Icon(Icons.abc_rounded),
)
],
),
),
)
.toList())),
);
}
}
In _MyHomePageState try to use employeeController.empData.value instead of employeeController.empData.
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Obx(() => Row(
children: employeeController.empData.value
.map(
(e) => Expanded(
child: Column(
children: [
Text(e.message),
Text(e.toString()),
IconButton(
onPressed: () {
employeeController.deleteEmployee(e.message);
},
icon: Icon(Icons.abc_rounded),
)
],
),
),
)
.toList())),
);
}
}
I am new to flutter and am kinda lost on how to set up a time to my splash screen so after this time it goes to the main screen. am using rive for the splash screen
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
void main() {
runApp(const MaterialApp(home: SimpleAnimation()));
}
class SimpleAnimation extends StatelessWidget {
const SimpleAnimation({Key? key, this.loading}) : super(key: key);
final bool? loading;
#override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: RiveAnimation.asset('assets/splash/splash.riv',
fit: BoxFit.cover)
),
);
}
}
You can set 3 second time in initstate after navigate to which screen you want
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
// TODO: implement initState
super.initState();
// after 3 second it will navigate
Future.delayed(const Duration(seconds: 3)).then((val) {
// Navigation Here
});
}
#override
Widget build(BuildContext context) {
return const Scaffold(
// your code
);
}
}
#override
void initState() {
//set timer for splash screen
Timer(const Duration(seconds: 4), () async {
//add your logic here
Navigator.pushNamedAndRemoveUntil(
context, ScreenRoute.mainScreen, (route) => false);
super.initState();
}
This SimpleAnimation widget shows after the splash screen. While this is StatelessWidget widget, you can define method inside build method. Change Duration(seconds: 2) based on your need.
class SimpleAnimation extends StatelessWidget {
const SimpleAnimation({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
Future.delayed(const Duration(seconds: 2)).then((value) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const NextScreen(),
));
});
return const Scaffold(
body: Center(
As folks already mentioned the straighforward way would be to add a delay and do navigation after it:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: SplashScreen(),
);
}
}
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
Future.delayed(const Duration(seconds: 2), () {
if (mounted) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const MainScreen(),
),
);
}
});
}
#override
Widget build(BuildContext context) {
return const ColoredBox(color: Colors.green);
}
}
class MainScreen extends StatelessWidget {
const MainScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const ColoredBox(color: Colors.yellow);
}
}
Though, with this implementation, you'll have to depend on the animation length. So when you'll update animation - you'll have not to forget to update it inside the splash screen. A more reliable (and complex) solution would be to listen to the animation status and do the navigation after the animation finishes. Like this (warning, change ):
class PlayOneShotAnimation extends StatefulWidget {
const PlayOneShotAnimation({Key? key}) : super(key: key);
#override
_PlayOneShotAnimationState createState() => _PlayOneShotAnimationState();
}
class _PlayOneShotAnimationState extends State<PlayOneShotAnimation> {
late RiveAnimationController _controller;
#override
void initState() {
super.initState();
_controller = OneShotAnimation(
'bounce',
autoplay: true,
onStop: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) => const MainScreen(),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RiveAnimation.network(
'https://cdn.rive.app/animations/vehicles.riv',
animations: const ['idle', 'curves'],
controllers: [_controller],
),
),
);
}
}
class MainScreen extends StatelessWidget {
const MainScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const ColoredBox(color: Colors.yellow);
}
}
This is my approach for splash screen, the advantage of this approach is to make sure that the splash screen launch only once when the app starting.
First define a static bool in app home class to indicate the app launch.
static bool launch = true;
Then at the home attribute in your MaterialApp widget at app home class, check if (launch) is true use a FutureBuilder to launch the splash screen, if (launch) is false set home to your second screen. With FutureBuilder you can set a timer for your splash screen, when it done your second screen will start (credit to https://stackoverflow.com/a/68699447/11619215).
home: launch? FutureBuilder(
future: Future.delayed(const Duration(seconds: 3)),
builder: (ctx, timer) =>
timer.connectionState == ConnectionState.done
? const SecondScreen(title: 'Flutter Demo Home Page')
: appSplashScreen(),
): const SecondScreen(title: 'Flutter Demo Home Page'),
In the Second screen, check if (launch) is true then set it to false. This will make sure that the splash screen will only launch once each time your application start.
if(AppHome.launch) {
AppHome.launch = false;
}
Below is the full code with appSplashScreen widget at the bottom:
import 'package:flutter/material.dart';
void main() {
runApp(const AppHome());
}
class AppHome extends StatelessWidget {
const AppHome({Key? key}) : super(key: key);
//static bool to indicate the launching of the app
static bool launch = true;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: launch? FutureBuilder(
future: Future.delayed(const Duration(seconds: 3)),
builder: (ctx, timer) =>
timer.connectionState == ConnectionState.done
? const SecondScreen(title: 'Flutter Demo Home Page')
: appSplashScreen(),
): const SecondScreen(title: 'Flutter Demo Home Page'),
);
}
}
class SecondScreen extends StatefulWidget {
const SecondScreen({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<SecondScreen> createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
#override
Widget build(BuildContext context) {
//mack sure your splash screen only launch once at your app starting
if(AppHome.launch) {
AppHome.launch = false;
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: const Center(
child: Text(
'My Second screen',
),
),
);
}
}
Widget appSplashScreen() {
return Container(
decoration: const BoxDecoration(
////you can add background image/color to your splash screen
// image: DecorationImage(
// image: AssetImage('assets/background.png'),
// fit: BoxFit.cover,
// ),
color: Colors.white,
),
child: Center(
child: SizedBox(
//get MediaQuery from instance of window to get height and width (no need of context)
height: MediaQueryData.fromWindow(WidgetsBinding.instance.window).size.height*0.5,
width: MediaQueryData.fromWindow(WidgetsBinding.instance.window).size.width*0.5,
child: Column(
children: const [
////you can add image to your splash screen
// Image(
// image: AssetImage('assets/splashscreen_image.png'),
// ),
FittedBox(
child: Text(
'Loading',
textAlign: TextAlign.center,
style: TextStyle(
decoration: TextDecoration.none,
),
)
),
CircularProgressIndicator(),
],
),
),
),
);
}
enter image description here
Here I want to change the button which depends on a text field, like when the text field is filled then show the button C, and when clicked the C button then change the button name C to AC and also need to change text field fill to empty.
check this example to demonstrate the output you need
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Location',
theme: ThemeData(
primarySwatch: Colors.amber,
),
home: const MyHomePage(title: 'Flutter Location Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, this.title}) : super(key: key);
final String? title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final TextEditingController _controller = TextEditingController();
String? buttonText;
#override
void initState() {
_controller.addListener(_checkTextIsEmpty);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title!),
),
body: Center(
child: Column(
children: [
TextField(
controller: _controller,
onChanged: (value) {},
),
Text(buttonText ?? ''),
],
),
),
);
}
void _checkTextIsEmpty() {
final value = _controller.text.isEmpty ? "AC" : "C";
setState(() {
buttonText = value;
});
}
}
Even after reading this and this, I still can't seem to wrap my head around storing page states in Flutter.
I've built a sample app, which has a main page called MyHomePage and a second page called SecondPage. MyHomePage has a floating action button, which displays SecondPage via Navigator.push(...). The second page contains a text field with an assigned controller. I would like to preserve the text field's text after I close and reopen SecondPage.
I've tried all sorts of combinations with setting buckets, page states and keys (inspired by the links above), but I couldn't make it work.
Also I'd like to store the whole page state automatically - without the need to write/retrieve every single value manually (in case I have a lot of text fields on the page).
Here is my code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
PageStorageKey mykey = new PageStorageKey("testkey");
class MyApp extends StatelessWidget {
final PageStorageBucket _bucket = new PageStorageBucket();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: PageStorage(
bucket: _bucket,
child: MyHomePage(),
)
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("State demo"),
),
body: Center(
child: Column(
children: <Widget>[
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _openSecondPage,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
_openSecondPage() {
Navigator.push(context, new MaterialPageRoute(builder: (context) => new SecondPage()));
}
}
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
final _aController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second page"),
),
body: Center(
child: TextField(
controller: _aController,
key: mykey,
autofocus: true,
),
)
);
}
}
EDIT:
Based on Ajay's answer, I was able to greatly simplify the working code. Turns out that in order to persist widget states manually, all you need is an instance of PageStorageBucket in combination with ValueKey instances.
Here are the modifications I did to Ajay's code:
Removed the after_layout plugin (initState method is sufficient).
Removed the global PageStorageKey instance (replaced it with a local ValueKey instance in the page that needs to use it).
Removed global instance of PageStorageBucket and replaced it with a final instance in MyApp, which is passed to the pages that need it via constructor attributes.
Removed PageStorage from the component tree.
Here is the resulting code (simplest working form):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final bucket = PageStorageBucket();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(bucket: bucket,),
);
}
}
class MyHomePage extends StatefulWidget {
final PageStorageBucket bucket;
const MyHomePage({Key key, this.bucket}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("State demo"),
),
body: Center(
child: Column(
children: <Widget>[],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _openSecondPage,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
_openSecondPage() {
Navigator.push(
context, new MaterialPageRoute(builder: (context) => new SecondPage(bucket: widget.bucket,)));
}
}
class SecondPage extends StatefulWidget {
final PageStorageBucket bucket;
const SecondPage({Key key, this.bucket}) : super(key: key);
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
static const KEY_A = ValueKey("secondPage.A");
final _aController = TextEditingController();
#override
void initState() {
super.initState();
_aController.addListener(_updateValue);
String value = widget.bucket.readState(context, identifier: KEY_A) ?? "";
_aController.text = value;
}
_updateValue() {
widget.bucket.writeState(context, _aController.text, identifier: KEY_A);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second page"),
),
body: Center(
child: TextField(
controller: _aController,
autofocus: true,
),
),
);
}
}
you need to read and write the state as well.
Check out the below code.
Note: I have used after_layout to initialize the text controller.
import 'package:after_layout/after_layout.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
PageStorageKey mykey = new PageStorageKey("testkey");
final PageStorageBucket _bucket = new PageStorageBucket();
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: PageStorage(
bucket: _bucket,
child: MyHomePage(),
));
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("State demo"),
),
body: Center(
child: Column(
children: <Widget>[],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _openSecondPage,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
_openSecondPage() {
Navigator.push(
context, new MaterialPageRoute(builder: (context) => new SecondPage()));
}
}
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage>
with AfterLayoutMixin<SecondPage> {
final _aController = TextEditingController();
#override
void initState() {
super.initState();
_aController.addListener(_updateValue);
}
#override
void afterFirstLayout(BuildContext context) {
String value =
_bucket.readState(context, identifier: ValueKey(mykey)) ?? "";
print(value);
_aController.text = value;
}
_updateValue() {
_bucket.writeState(context, _aController.text, identifier: ValueKey(mykey));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second page"),
),
body: Center(
child: TextField(
controller: _aController,
key: mykey,
autofocus: true,
),
),
);
}
}