How to load different pdf page on initial load - flutter

I'm using syncfusion_flutter_pdfviewer package for my flutter project, and I'm trying to pass a specific page number from my Main Page to my Details page. After that it'll load that PDF page number immediacy when the Detail page is open, but I'm stuck, so any help or suggestion would be really appreciated.
I have tried like this, but I'm getting an error on "jumpToPage"
This expression has a type of 'void' so its value can't be used.
Try checking to see if you're using the correct API; there might be a function or call that returns void you didn't expect. Also check type parameters and variables which might also be void.
body: SfPdfViewer.asset(
'data/hymn_pdf/full-songs.pdf',
controller: _pdfViewerController.jumpToPage(widget.number),
),
Full Details page Code.
class DisplayScreen extends StatefulWidget {
final int number;
const DisplayScreen(this.number);
#override
_DisplayScreen createState() => _DisplayScreen();
}
class _DisplayScreen extends State<DisplayScreen> {
final GlobalKey<SfPdfViewerState> _pdfViewerKey = GlobalKey();
late PdfViewerController _pdfViewerController;
#override
void initState() {
_pdfViewerController = PdfViewerController();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Syncfusion Flutter PdfViewer'),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.arrow_drop_down_circle,
color: Colors.white,
),
onPressed: () {
_pdfViewerController.jumpToPage(widget.number); // I got this from synfusion doc and I'm trying to copy it in my controller but I'm getting an error.
},
),
],
),
body: SfPdfViewer.asset(
'data/hymn_pdf/full-songs.pdf',
controller: _pdfViewerController.jumpToPage(widget.number), //I'm getting an error here
),
);
}
}

You cannot just call jumpToPage when assigning the controller. Wrong example:
body: SfPdfViewer.asset(
'data/hymn_pdf/full-songs.pdf',
controller: _pdfViewerController.jumpToPage(widget.number), // <-- nope
),
You need to call the method after the pdf is loaded. For example, call it in the onDocumentLoaded callback function.
Example code:
SfPdfViewer.asset(
'data/hymn_pdf/full-songs.pdf',
controller: _pdfViewerController,
onDocumentLoaded: (details) { // as soon as the doc is loaded
_pdfController.jumpToPage(3); // jump to page 3
},
)

Related

Dispose the controller when navigating to same page

I've got a search-field in the appbar of my application. It's available on every page of the app. When something is selected in the seach-field, it routes to a named page that loads and displays details about the entity. It is also possible to use the search bar from the details page, which means, that it routes to the same page that it's already on. As the id of the entity is part of the url, the route is actually different and as such everything works. But...
When the page is opened, my GetX controller does some initialization, and on leave it disposes of resources such as TextController.
My problem is that when I navigate to the same page, onClose is not called before leaving which means that the resources are not disposed. Rather it open the 'new' page, calls onInit() then calls onClose() and disposes the resources that are in use.
When the action of the seach-field determines that the currentRoute is already on the details-page, it uses Get.offNamed('/details', parameters({id: entityId})), if it is on another page it simply uses Get.toNamed('/details', parameters({id: entityId}))
I've made a little app the illustrates the problem. The search-field has been replaced by buttons for simplicity.
https://flutlab.io/editor/994c9029-47e9-46e2-a0e6-cfd970b82079
To make it fail, try pressing 'To subpage' button twice.
Notice in the console:
Initialized
Initialized
Closed
I was expecting:
Initialized
Closed
Initialized
Closed
For reference this is the code:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
final Random random = Random();
final appBar = AppBar(
// The title text which will be shown on the action bar
title: const Text('Hello'),
actions: [
ElevatedButton(
child: const Text('To Subpage'),
onPressed: () {
if (Get.currentRoute.startsWith('/subpage')) {
Get.offNamed('/subpage',
parameters: {'id': '${random.nextInt(1000)}'});
} else {
Get.toNamed('/subpage',
parameters: {'id': '${random.nextInt(1000)}'});
}
}),
ElevatedButton(
child: const Text('Go Home'),
onPressed: () {
Get.offAllNamed('/', parameters: {'id': '${random.nextInt(1000)}'});
})
],
);
final pages = [
GetPage(name: '/', page: () => RootPage()),
GetPage(name: '/subpage', page: () => SubPage(), binding: PageBinding()),
];
class PageBinding implements Bindings {
#override
void dependencies() {
Get.put(SubPageController());
}
}
class SubPageController extends GetxController {
final TextEditingController myDomController = TextEditingController();
String? id;
#override
void onInit() {
id = Get.parameters['id'];
print('initialized with id $id');
// Now initialize and load a lot of stuff based on id
super.onInit();
}
#override
void onClose() {
print('closed with id $id');
myDomController.dispose(); // This is a problem!
super.onClose();
}
}
void main() => runApp(GetMaterialApp(
getPages: pages,
initialRoute: '/',
));
class RootPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: appBar,
body: const Center(
child: Text(
'Root page',
),
),
);
}
}
class SubPage extends GetView<SubPageController> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: appBar,
body: Center(
child: Column(
children: [
Text(
'Subpage page',
),
TextFormField(controller: controller.myDomController)
],
)),
);
}
}
```
in such cases for you, you can delete (run onClose()) the controller you want simply by using:
Get.delete<SubPageController>();
if you have a tag for that controller:
Get.delete<SubPageController>(tag: yourTagHere);
so in the onPressed of your buttons, you could call to delete the controller from the memory, then call it for your next use:
ElevatedButton(
child: const Text('Go Home'),
onPressed: () {
Get.delete<SubPageController>();
Get.offAllNamed('/', parameters: {'id': '${random.nextInt(1000)}'});
})
this will delete the controller if it's already on, then navigate to that root so it will be called again, and so on.

How to update BottomNavigationBar Widget when receiving notifications

I'm fairly new to flutter and am having trouble getting my BottomNavigationBar to update when my app receives a notification (eg. a new message in the users inbox). I've tried a couple solutions, like adding a listener in the 'initState' function, but it seems to cause a memory leak issue.
Here is my basic structure of the app in pseudo code:
main.dart
import 'globals.dart' as globals;
#override
void initState() {
addListeners();
super.initState();
}
void addListeners() {
FirebaseMessaging.onMessage.listen((RemoteMessage event) {
switch (event.data["type"]) {
case 'message':
debugPrint("New messages received");
globals.myInbox.value = data["messageCount"];
break;
}
}
}
... [All routes defined here] ...
layout.dart
import 'globals.dart' as globals;
class StandardLayout extends StatefulWidget {
Widget InboxCount() {
int messageCount = globals.myInbox;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Colors.blue,
),
body: Container(
height: double.infinity,
width: double.infinity,
color: Colors.white60,
child: widget.output,
),
bottomNavigationBar:
BottomNavigationBar(
type: BottomNavigationBarType.fixed,
... [Other config options] ..
items: [
BottomNavigationBarItem(
label: 'Home',
icon: Icon(Icons.home),
),
BottomNavigationBarItem(
label: 'Schedule',
icon: Icon(Icons.calendar_today),
),
BottomNavigationBarItem(
label: 'Time Off',
icon: Icon(Icons.beach_access),
),
BottomNavigationBarItem(
label: 'Messages',
icon: Stack(
children: [
Icon(Icons.messenger),
InboxCount() //Stacks the badge on this item
],
)
)
],
)
);
}
}
example.dart
import 'layout.dart';
class ExampleScreen extends StatelessWidget {
const ExmapleScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StandardLayout('title': 'Example Screen', 'output': Container(child: Text('Hello World!')));
}
}
Attempt #1:
If I add a listener to the layout.dart like so:
#override
void initState() {
globals.myInbox.addListener(() {
//Refresh the layout
debugPrint('Reloading Layout');
setState(() {});
});
super.initState();
}
it sort of works, but it fires mutliple times as I can see the debugPrint message being displayed 2,3,4,5 even upto 6 times. I will also sometimes get messages saying the state is unreachable, been disposed, or warnings about memory leaks. So obviously it is getting fired too many times.
Attempt #2
If I add the listener to the main.dart file, the listener event only gets fired once when the globals.myInbox value changes, so thats good! Unfortunately, the bottom navigation in layout.dart doesn't get updated obviously because I can't send a 'setState' to another file.
So now what?
Is my structure of a dart/flutter program completely wrong that it won't allow me to do what I want? Where do I put the listener or how do I implement it so the bottom navigation will update when "globals.myInbox" gets updated?
Hopefully that makes sense, I omitted a bunch of code that isn't relevant to the problem.

Flutter-web TextFormField issue

usually I can disable/grey-out a button until a TextFormField meets certain parameters in flutter by something like this:
TextFormField(
controller: _controller
value: (value)
)
SubmitButton(
onPressed: _controller.text.isNotEmpty ? _submit : null;
)
But when compiled as a website the Button seems no longer aware of the controller value...
I have tried targeting in several different ways, e.g. _controller.value.text.isEmpty and _controller.text.isEmpty...
I'm guessing I'm missing something or this method just isn't possible for web ... Is there any other way to get the same result?
To be honest, your code shouldn't work in flutter mobile either, but may be works because of screen keyboard causes widget rebuild when showing or hiding.
To fix this issue we have to use stateful widget with state variable like canSubmit and update it in textField's listener onChange with setState method. Then every time the text changes, our stateful widget will update the submit button..
class Page extends StatefulWidget {
#override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> {
bool canSubmit;
#override
void initState() {
canSubmit = false;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
onChanged: (value) {
setState(() {
canSubmit = value.isNotEmpty;
});
},
),
RaisedButton(
onPressed: canSubmit ? _submit : null,
child: Text('Submit'),
)
],
),
),
);
}
void _submit() {
print('Submitted');
}
}

does setState work on objects in dart/flutter?

I have a flutter widget that attempts to solve soduku grids. I have class called SodukuSolver which does all the calculations and provides a List<String> of the current results. I call setState to refresh the list, but it does not update the screen.
Below, I'll try to include as much of the relevant code as I can. Full source is at https://github.com/mankowitz/soduku
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(title: "Soduku solver", home: Soduku());
}
}
class SodukuState extends State<Soduku> {
SodukuSolver ss;
List<String> _list;
int _changes = 0;
int _remaining = 81;
#override
Widget build(BuildContext context) {
final String _starting =
"750943002024005090300020000140089005093050170500360024000070009070400810400198057";
ss = new SodukuSolver(_starting);
_list = ss.getList();
return Scaffold(
appBar: AppBar(title: Text('Soduku solver'), actions: <Widget>[
// action button
IconButton(
icon: Icon(Icons.directions_walk),
onPressed: () {
_iterate();
},
),
]),
body: _buildGrid(),
);
}
Widget _buildGrid() {
return Column(children: <Widget>[
AspectRatio(
aspectRatio: 1.0,
child: Container(
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 9,
),
itemBuilder: _buildGridItems,
itemCount: 81,
),
),
),
]);
}
Widget _buildGridItems(BuildContext context, int index) {
return GestureDetector(
child: GridTile(
child: Container(
child: Center(
child: Text(_list[index]),
),
),
),
);
}
void _iterate() {
setState(() {
_changes = ss.iterateSoduku();
_remaining = ss.empties();
_list = ss.getList();
});
}
}
class Soduku extends StatefulWidget {
#override
SodukuState createState() => SodukuState();
}
So the problem is that _iterate() is being called, and I can use the debugger to see that the internal state of SodukuSolver is being updated and it is even passing _list correctly, but the grid on screen doesn't update, even though _changes and _remaining do update.
You are creating new SodukuSolver with same _starting every time the widget builds and then obtaining _list from it. So you are overriding changes from previous iteration.
Looks like SodukuSolver creation should be performed once. You can override initState in SodukuState and initialise SodukuSolver there or initialise it in the same place where it is declared
Just add your code in the initState() method as following
#override
void initState() {
super.initState();
final String _starting =
"750943002024005090300020000140089005093050170500360024000070009070400810400198057";
ss = new SodukuSolver(_starting);
_list = ss.getList();
}
In your case, your list is not getting updated as setState() method will call your SodukuSolver() and ss.getList(); methods every time. because, setSate() ultimately calls build method to render every time.
So adding it inside your initState method will solve your issue. As it is getting called only once when the screen/route initialises.

How to apply MVC or design pattern in flutter?

I am trying to include biometric authentication using local_auth package. This is used when the app starts. The fingerprint is used to determine whether the user is the owner of the phone. If it is confirmed, they will be taken to the home page. The below code works but what I would like to apply on the below code is MVC or design pattern. Can someone guide me?
class LoginOptionState extends State<LoginOption> {
final LocalAuthentication auth = LocalAuthentication();
String _authorized = 'Not Authorized';
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Container(
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Column(
children: <Widget>[
Text("Touch ID"),
SizedBox(height: 10),
GestureDetector(
child: Image.asset(
"assets/",
),
onTap: _authenticate),
],
),
],
),
)));
}
Future<void> _authenticate() async {
bool authenticated = false;
try {
authenticated = await auth.authenticateWithBiometrics(
localizedReason: 'Scan your fingerprint to authenticate',
useErrorDialogs: true,
stickyAuth: false);
} on PlatformException catch (e) {
print(e);
}
if (!mounted) return;
setState(() {
_authorized = authenticated
? Navigator.pushNamed(context, homePageViewRoute)
: 'Not Authorized';
});
}
}
Use the excellent library by Greg Perry mvc_pattern. Quick start sample code and explanation is provided on the link.
Here is a quick start example of the classic counter app, from the link above:
The view:
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
// Fields in a Widget subclass are always marked "final".
static final String title = 'Flutter Demo Home Page';
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final Controller _con = Controller.con;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
widget.title,
),
Text(
'${_con.counter}',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(
_con.incrementCounter
);
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Controller class:
class Controller extends ControllerMVC {
/// Singleton Factory
factory Controller() {
if (_this == null) _this = Controller._();
return _this;
}
static Controller _this;
Controller._();
/// Allow for easy access to 'the Controller' throughout the application.
static Controller get con => _this;
int get counter => _counter;
int _counter = 0;
void incrementCounter() => _counter++;
}
Now the above code refactored, to add a model:
int get counter => Model.counter;
void incrementCounter() {
/// The Controller knows how to 'talk to' the Model. It knows the name, but Model does the work.
Model._incrementCounter();
}
And finally the model class:
class Model {
static int get counter => _counter;
static int _counter = 0;
static int _incrementCounter() => ++_counter;
}
However make sure you upgrade flutter to version 1.13.0. At least for me, I was getting several build errors in lower versions.
Karee is a set of tools that implementes MVC design in Flutter. It help you to manage your Controllers, your routes, your screens and more. Refer to karee github wiki to get documentation.
You Can use Karee . It supports Flutter 2.X.X
To installation run
npm install -g karee
Then karee create
After creating a New Flutter project based on Karee you can add new controller
Sample Code
Creating à New controller
Karee generate --controller --path auth Authentication
Generated file under lib/app/controllers/auth/AuthenticationController
#Controller
class AuthenticationController {
login(username, password) {
/// Your custom code
}
}
Add route
Route.on('/login', 'AuthenticationController#login');
Use in your Screen
var authUser = KareeRouter.goto('/login', parameter:[username, password]);