Flutter first launch page - dart

I'm currently working on a Flutter version of an android application and now want to implement the first launch page. It should only be displayed on the very first launch of the app after it has been installed on a device. I figured out how to do it for the android app, but I only have a basic knowledge of Flutter, so I'm asking this question.
Does anyone know how to do this in Flutter in a simple way?
Thanks in advance for your help!

You could do this by having a separate route for your intro/app:
void main() {
runApp(new MaterialApp(
home: new MyIntroPage(),
routes: <String, WidgetBuilder> {
'/app': (BuildContext context) => new MyAppPage()
},
));
}
In your intro page you could check a flag for whether the user has seen it (see here for some ideas on persisting data) and if so, just navigate straight to the app (for example like the _completeLogin function here)

You could make the Splash screen in Dart but is not recommend because it can bore your users (you will use a fixed duration time) , so in my personal opinion I will go for a native splash screen in each platform.
Android
Modify the file launch_background.xml
Uncomment the bitmap item and
put any item that you have in your mipmap or drawable folder
Change the color of your background in android:drawable item
iOS
Add your custom images into the Assets.xcassets folder
Modify the file LaunScreen.storyboard, change the background color, add and imageview, etc.
I created a post with all of the details about the Splash Screen in Flutter, you can take a look : https://medium.com/#diegoveloper/flutter-splash-screen-9f4e05542548

First-time view Page with user input, It's sample code, you can get idea
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:reborn_next_job02/ui/LoginScreen.dart';
import 'package:reborn_next_job02/ui/splashScreen.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Splash extends StatefulWidget {
#override
SplashState createState() => new SplashState();
}
class SplashState extends State<Splash> {
Future checkFirstSeen() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool _seen = (prefs.getBool('seen') ?? false);
if (_seen) {
Navigator.of(context).pushReplacement(
new MaterialPageRoute(builder: (context) => new LoginScreen()));
} else {
prefs.setBool('seen', true);
Navigator.of(context).pushReplacement(
new MaterialPageRoute(builder: (context) => new urlScreen()));
}
}
#override
void initState() {
super.initState();
new Timer(new Duration(seconds: 10), () {
checkFirstSeen();
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Text('Loading...'),
),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Hello'),
),
body: new Center(
child: new Text('This is the second page'),
),
);
}
}
class IntroScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Text('This is the intro page'),
new MaterialButton(
child: new Text('Gogo Home Page'),
onPressed: () {
Navigator.of(context).pushReplacement(
new MaterialPageRoute(builder: (context) => new Home()));
},
)
],
),
),
);
}
}

I don't really Know the exact Logic but This is How I would implement it,
I will be just explaining you the logic the code below is not syntactically correct
lets call your one time view page as intro page
I will maintain an integer variable in that intro page
int counter;
When for the first Time the intro page loads
I will initialize
counter = sharedPreferencevalue // defaults to 0 when App is fresh installed
and check
if counter==0
if yes
load the intro page
counter++;
store counter in SharedPreference; //overwrite the default value
else
go to Home
simply increment the variable and store the value of that variable locally using SharedPreference
for those who don't know about Shared Preferences, it allows a Flutter application (Android or iOS) to save settings, properties, data in the form of key-value pairs that will persist even when the user closes the application.
hope this Helps :)

Doesn't help you in remove back button but i think it will help you in making splash screen I developed a package the Link

Please find the repo.
If you want to take a look at Flutter you can find some good examples at our company’s Github page. Also, you can check our company's page FlutterDevs.

Related

InheritedWidget not accessible in new route

I'm new to Flutter and confused about how InheritedWidget works with routes. I'm using an SQLite database with the sqflite library. Basically, what I'm trying to achieve is, when my app is launched, I want all widgets that don't require the database to show right away. For instance, the bottomNavigationBar of my Scaffold doesn't need the database but the body does. So I want the bottomNavigationBar to show right away, and a CircularProgressIndicator to be shown in the body. Once the database is open, I want the body to show content loaded from the database.
So, in my attempt to achieve this, I use FutureBuilder before my Scaffold to open the database. While the Future is not completed, I pass null for the drawer and a CircularProgressBar for the body, and the bottomNavigationBar as normal. When the Future completes, I wrap the drawer and body (called HomePage) both with their own InheritedWidget (called DataAccessor). This seems to work, as I can access the DataAccessor in my HomePage widget. But, when I use the Navigator in my drawer to navigate to my SettingsScreen, my DataAccessor is not accessible and returns null.
Here's some example code, not using a database but just a 5 second delayed Future:
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: FutureBuilder(
future: Future.delayed(Duration(seconds: 5)),
builder: (context, snapshot) {
Widget drawer;
Widget body;
if (snapshot.connectionState == ConnectionState.done) {
drawer = DataAccessor(
child: Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Settings"),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SettingsScreen()))
)
]
)
)
);
body = DataAccessor(child: HomePage());
}
else {
drawer = null;
body = Center(child: CircularProgressIndicator());
}
return Scaffold(
drawer: drawer,
body: body,
bottomNavigationBar: BottomNavigationBar(
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Container(), title: Text("One")),
BottomNavigationBarItem(icon: Container(), title: Text("Two"))
]
)
);
}
)
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
DataAccessor dataAccessor = DataAccessor.of(context); //dataAccessor IS NOT null here
print("HomePage: ${dataAccessor == null}");
return Text("HomePage");
}
}
class SettingsScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
DataAccessor dataAccessor = DataAccessor.of(context); //dataAccessor IS null here
print("SettingsScreen: ${dataAccessor == null}");
return Text("SettingsScreen");
}
}
class DataAccessor extends InheritedWidget {
DataAccessor({Key key, Widget child}) : super(key: key, child: child);
#override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
static DataAccessor of(BuildContext context) => context.inheritFromWidgetOfExactType(DataAccessor);
}
It's possible I'm doing things wrong. Not sure how good of practice storing widgets in variables is. Or using the same InheritedWidget twice? I've also tried wrapping the entire Scaffold with my DataAccessor (and having the database as null while it is loading), but the issue still remains where I can't get my DataAccessor in my SettingsScreen.
I've read that a possible solution is to put my InheritedWidget before the MaterialApp but I don't want to resort to this. I don't want a whole new screen to show while my database is opening, I want my widgets that don't need the database to be shown. This should be possible somehow.
Thanks!
The solution in the last paragraph is what you need. The MaterialApp contains the Navigator which manages the routes, so for all of your routes to have access to the same InheritedWidget that has to be above the Navigator, i.e. above the MaterialApp.
Use Remi's method and you end up with a widget tree like this:
MyApp (has the static .of() returning MyAppState)
MyAppState, whose build returns _MyInherited(child: MaterialApp(...)) and whose initState starts loading the database, calling setState when loaded.
When building your home page you have access to MyAppState via .of, so can ascertain whether the database has loaded or not. If it has not, just build the database independent widgets; if it has, build all the widgets.

Flutter camera frame drops when switching between pages

I have tried to use the Flutter camera plugin (0.2.1) in combination with a PageView and a BottomNavigationBar, but everytime the page gets switched, a few frames get skipped and the UI freezes for a second.
I've simplified my codebase for this example:
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
void main() => runApp(new Pages());
class Pages extends StatefulWidget {
#override
_PagesState createState() => _PagesState();
}
class _PagesState extends State<Pages> {
PageController _pageController;
int _page = 0;
#override
void initState() {
_pageController = new PageController();
super.initState();
}
void navTapped(int page) {
_pageController.animateToPage(page,
duration: const Duration(milliseconds: 300), curve: Curves.ease);
}
void onPageChanged(int page) {
setState(() {
this._page = page;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: new Text("CameraTest"),
),
body: PageView(
children: <Widget>[Feed(), Camera(), Profile()],
controller: _pageController,
onPageChanged: onPageChanged,
),
bottomNavigationBar: new BottomNavigationBar(
items: [
new BottomNavigationBarItem(
icon: new Icon(Icons.home), title: new Text("Feed")),
new BottomNavigationBarItem(
icon: new Icon(Icons.camera), title: new Text("Capture")),
new BottomNavigationBarItem(
icon: new Icon(Icons.person), title: new Text("Profile"))
],
onTap: navTapped,
currentIndex: _page,
),
),
);
}
}
class Camera extends StatefulWidget {
#override
_CameraState createState() => _CameraState();
}
class _CameraState extends State<Camera> {
List<CameraDescription> _cameras;
CameraController _controller;
initCameras() async{
_cameras = await availableCameras();
_controller = new CameraController(_cameras[0], ResolutionPreset.medium);
await _controller.initialize();
setState(() {});
}
#override
void initState() {
initCameras();
super.initState();
}
#override
void dispose() {
_controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if (_controller == null || !_controller.value.isInitialized) {
return new Center(
child: new Text("Waiting for camera...", style: TextStyle(color: Colors.grey),),
);
}
return new AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: new CameraPreview(_controller));
}
}
//just placeholder widgets
class Feed extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(child: new Text("Feed"));
}
}
class Profile extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(child: new Text("Profile"));
}
}
There are basically three pages with the middle one showing a camera-preview
(how it's supposed to look), but on switching to the camera and back from it this happens. This is really annoying since it ruins the user experience and is not smooth at all. The lag appears when calling initCameras() or when disposing the camera-controller. I tried using initCameras() in combination with a FutureBuilder, which didn't help at all, and running the method in a seperate isolate, but platform calls seem to be only allowed on the main isolate. It seems a bit weird to me since opening the camera doesn't need too much cpu power, so an async method should be fine. I am aware there is an image-picker plugin, but I want to have the preview in the app directly. I have also considered to run initCameras() on app start, but i don't want to have the camera running all the time when the user is just using another page of the app.
Is there any way to improve upon initCameras() or perhaps use a different implementation to fix the stuttering? I wouldn't even care if it takes a second to load, but i don't want any frame skips.
I followed the example on the bottom of the camera page.
Tested on physical devices as well as emulators on different Android versions.
I have solved a similar issue by adding a delay before initializing the camera:
Future.delayed(Duration(seconds: 1), () {
initCameras();
});
This way the initCameras() is called after the page navigation animation is completed. You can show a CircularProgressIndicator() to make this delay more user friendly.
I am sure there is a more neat way to work around the issue, but this seems to be the simplest solution.
If you want to use image path in the different page than store image path as the global variable, and use it where you want.

How to set state from another widget?

I'm trying to change the state from a different widget in Flutter. For example, in the following example I set the state after a few seconds.
Here is the code for that:
class _MyAppState extends State<MyApp> {
int number = 1;
#override
void initState() {
super.initState();
new Future.delayed(new Duration(seconds: 5)).then((_) {
this.setState(() => number = 2);
print("Changed");
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new FlatButton(
color: Colors.blue,
child: new Text("Next Page"),
onPressed: () {
Navigator.of(context).push(new MaterialPageRoute(
builder: (BuildContext context) => new StatefulBuilder(builder: (BuildContext context, setState) =>new MySecondPage(number))
));
},
),
),
);
}
}
I tried using an InheritedWidget, but that won't work unless I wrap it around my top level widget, which is not feasible for what I'm trying to do (the code above is a simplification of what I'm trying to achieve).
Any ideas on what the best way of achieving this is in Flutter?
Avoid this whenever possible. It makes these widgets depends on each others and can make things harder to maintain in the long term.
What you can do instead, is having both widgets share a common Listenable or something similar such as a Stream. Then widgets interact with each other by submitting events.
For easier writing, you can also combine Listenable/Stream with respectively ValueListenableBuilder and StreamBuilder which both do the listening/update part for you.
A quick example with Listenable.
class MyHomePage extends StatelessWidget {
final number = new ValueNotifier(0);
#override
Widget build(BuildContext context) {
return Scaffold(
body: ValueListenableBuilder<int>(
valueListenable: number,
builder: (context, value, child) {
return Center(
child: RaisedButton(
onPressed: () {
number.value++;
},
child: MyWidget(number),
),
);
},
),
);
}
}
class MyWidget extends StatelessWidget {
final ValueListenable<int> number;
MyWidget(this.number);
#override
Widget build(BuildContext context) {
return new Text(number.value.toString());
}
}
Notice here how we have our UI automatically updating when doing number.value++ without ever having to call setState.
Actually the most effective way to do this is using BLoC package in flutter and implement it from the top of the widget tree so all inheriting widgets can use the same bloc. If you have worked with Android before - it works like Android Architecture Components - you separate data and state management from the UI - so you do not setState in the UI, but instead use the block to manage state. So you can set and access the same data - from any widget that inherits from the top widget where the bloc is implemented, for more complex apps, it is very useful.
This is where you can find the package: https://pub.dev/packages/flutter_bloc#-readme-tab-
Write-up: https://www.didierboelens.com/2018/08/reactive-programming-streams-bloc/
And a great tutorial on youtube https://www.youtube.com/watch?v=hTExlt1nJZI&list=PLB6lc7nQ1n4jCBkrirvVGr5b8rC95VAQ5&index=7

Flutter Navigator not working

I have app with two screens, and I want to make push from 1st to second screen by pressing button.
Screen 1
import 'package:flutter/material.dart';
import './view/second_page.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new MainScreen();
}
}
class MainScreen extends State<MyApp> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Title")
),
body: new Center(
child: new FlatButton(child: new Text("Second page"),
onPressed: () {
Navigator.push(context,
new MaterialPageRoute(
builder: (context) => new SecondPage()))
}
)
)
)
);
}
}
Screen 2
import 'package:flutter/material.dart';
class SecondPage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new SecondPageState();
}
}
class SecondPageState extends State<SecondPage> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Title"),
),
body: new Center(
child: new Text("Some text"),
),
);
}
}
Push not happening and I got this
The following assertion was thrown while handling a gesture: Navigator
operation requested with a context that does not include a Navigator.
The context used to push or pop routes from the Navigator must be that
of a widget that is a descendant of a Navigator widget.
Another exception was thrown: Navigator operation requested with a
context that does not include a Navigator.
What is wrong?
Think of the widgets in Flutter as a tree, with the context pointing to whichever node is being built with the build function. In your case, you have
MainScreen <------ context
--> MaterialApp
(--> Navigator built within MaterialApp)
--> Scaffold
--> App Bar
--> ...
--> Center
--> FlatButton
So when you're using the context to find the Navigator, you're using a context for the MainScreen which isn't under the navigator.
You can either make a new Stateless or Stateful Widget subclass to contain your Center + FlatButton, as the build function within those will point at that level instead, or you can use a Builder and define the builder callback (which has a context pointing at the Builder) to return the Center + FlatButton.
Just make the MaterialApp class in main method as this example
void main() => runApp(MaterialApp(home: FooClass(),));
it works fine for me,
I hope it will work with you
There are two main reasons why the route cannot be found.
1) The Route is defined below the context passed to Navigator.of(context) - scenario which #rmtmackenzie has explained
2) The Route is defined on the sibling branch e.g.
Root
-> Content (Routes e.g. Home/Profile/Basket/Search)
-> Navigation (we want to dispatch from here)
If we want to dispatch a route from the Navigation widget, we have to know the reference to the NavigatorState. Having a global reference is expensive, especially when you intend to move widget around the tree. https://docs.flutter.io/flutter/widgets/GlobalKey-class.html. Use it only where there is no way to get it from Navigator.of(context).
To use a GlobalKey inside the MaterialApp define navigatorKey
final navigatorKey = GlobalKey<NavigatorState>();
Widget build(BuildContext context) => MaterialApp {
navigatorKey: navigatorKey
onGenerateRoute : .....
};
Now anywhere in the app where you pass the navigatorKey you can now invoke
navigatorKey.currentState.push(....);
Just posted about it https://medium.com/#swav.kulinski/flutter-navigating-off-the-charts-e118562a36a5
There is an another very different work around about this issue, If you are using Alarm Manager (Android), and open back to your Application. If you haven't turned on the screen before navigation, the navigator will never work. Although this is a rare usage, I think It should be a worth to know.
Make sure the route table mentioned in the same context:
#override
Widget build(BuildContext context) {
return MaterialApp(
home: FutureBuilder(
future: _isUserLoggedIn(),
builder: (ctx, loginSnapshot) =>
loginSnapshot.connectionState == ConnectionState.waiting ?
SplashScreen() : loginSnapshot.data == true ? AppLandingScreen(): SignUpScreen()
),
routes: {
AppLandingScreen.routeName: (ctx) => AppLandingScreen(),
},
);
}
I faced this issue because I defined the route table in different build method.
Am a newbie and have spent two days trying to get over the Navigtor objet linking to a black a screen.
The issue causing this was dublicated dummy data. Find Bellow the two dummny data blocks:
**Problematic data **- duplicate assets/image:
_buildFoodItem('assets/plate1.png', 'Salmon bowl', '\$24'),
_buildFoodItem('assets/plate2.png', 'Spring bowl', '\$13'),
_buildFoodItem('assets/plate1.png', 'Salmon bowl', '\$24'),
_buildFoodItem('assets/plate5.png', 'Berry bowl', '\$34'),
**Solution **- after eliminating duplicated image argument:
_buildFoodItem('assets/plate1.png', 'Salmon bowl', '\$24'),
_buildFoodItem('assets/plate2.png', 'Spring bowl', '\$13'),
_buildFoodItem('assets/plate6.png', 'Avocado bowl', '\$34'),
I hope this helps someone,,,,,,,
If the navigator is not working, it can be due to many reasons but the major one is that the navigator not finds the context.
So, to solve this issue try to wrap your widget inside Builder because the builder has its own context...

Exact timing of the display of images and user reactions in Flutter applications

I have to create an app for psychological reaction time testing. For that purpose I have to control exactly when an image is
visible and exactly measure the delay between the onset of the visibility of the image and the onset of the reaction (e.g. tap event).
How can I achieve that in Flutter? Specifically, what is the most efficient way to show and hide several different images in the same place and how can I exactly know the onset of the tap event in relation to the onset of the real and full visibility to the user taking into consideration the frame rate of the device? Is there way to get low-level control over that process. Flutter seems to expose a high-level api, usually.
I have made an attempt that is possibly exactly what you are looking for, my logic is as follows:
Add a listener to the image being presented.
Using Stopwatch class, I notify my object to start counting time once the image is displayed.
When clicking on the correct answer, I stop my Stopwatch to stop counting.
Save my current score, and carry on to the next question.
Note:
In this example, for the sake of simplicity, I did not make an account for which answer is correct and which is not.
I create a new StatelessWidget to hold each question, using PageView.builder might be a good use here as well.
Simple Example:
import 'dart:async';
import 'package:flutter/material.dart';
int score = 0;
class TimeTest extends StatefulWidget {
Widget nextQuestionWidget; //to navigate
String question;
NetworkImage questionImage;
List<String> answers;
TimeTest(
{this.questionImage, this.question, this.answers, this.nextQuestionWidget });
#override
_TimeTestState createState() => new _TimeTestState();
}
class _TimeTestState extends State<TimeTest> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
bool _loading = true;
Stopwatch timer = new Stopwatch();
#override
void initState() {
widget.questionImage.resolve(new ImageConfiguration()).addListener((_, __) {
if (mounted) {
setState(() {
_loading = false;
});
timer.start();
}
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(title: new Text("Time Test"),),
body: new Container(
alignment: FractionalOffset.center,
margin: const EdgeInsets.symmetric(vertical: 15.0),
child: new Column(
children: <Widget>[
new Text(widget.question),
new Divider(height: 15.0, color: Colors.blueAccent,),
new CircleAvatar(backgroundImage: widget.questionImage,
backgroundColor: Colors.transparent,),
new Container(height: 15.0,),
new Column(
children: new List.generate(widget.answers.length, (int index) {
return new FlatButton(
onPressed: () {
///TODO
///add conditions for correct or incorrect answers
///and some manipulation on the score
timer.stop();
score = score + timer.elapsedMilliseconds;
print(score);
_scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text(
"Your answered this question in ${timer
.elapsedMilliseconds}ms")));
///Hold on before moving to the next question
new Future.delayed(const Duration(seconds: 3), () {
Navigator.of(context).push(new MaterialPageRoute(
builder: (_) => widget.nextQuestionWidget));
});
}, child: new Text(widget.answers[index]),);
}),
)
],
),),
);
}
}
class QuestionOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new TimeTest(question: "Which animal is in this photo?",
questionImage: new NetworkImage(
"http://cliparting.com/wp-content/uploads/2016/09/Tiger-free-to-use-clipart.png"),
answers: ["Lion", "Tiger", "Cheetah"],
nextQuestionWidget: new QuestionTwo(),);
}
}
class QuestionTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new TimeTest(question: "Which bird is in this photo?",
questionImage: new NetworkImage(
"http://www.clker.com/cliparts/P/q/7/9/j/q/eagle-hi.png"),
answers: ["Hawk", "Eagle", "Falcon"],
nextQuestionWidget: new ResultPage(),);
}
}
class ResultPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text("Result"),),
body: new Center(
child: new Text("CONGRATULATIONS! Your score is $score milliseconds"),),
);
}
}
void main() {
runApp(new MaterialApp(home: new QuestionOne()));
}

Resources