I'm a beginner in flutter and I'm looking for a simple way to refresh a network image.
In a basic code like this, what would be the simplest method of getting flutter to fetch and draw this image again? In my code the image is a snapshot from a security camera, so it changes every time it is fetched, but always has the same url. I get a new picture every time I start the app, but I would like the image refreshed when I press the image itself.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
var title = 'Web Images';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Image.network('https://picsum.photos/250?image=9'),
),
);
}
}
Extend Your Class With Stateful Widget then:
body: Inkwell(
onTap: ()=> setState(){};
Image.network('https://picsum.photos/250?image=9'),
),
this will refresh the page. Or If you dont want to tap then :
#override
void initState() {
super.initState();
setState(){
print('refreshing');
}
}
If you need forced picture refresh - try such code:
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
var title = 'Web Images';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: ForcePicRefresh(),
));
}
}
class ForcePicRefresh extends StatefulWidget {
#override
_ForcePicRefreshState createState() => _ForcePicRefreshState();
}
class _ForcePicRefreshState extends State<ForcePicRefresh> {
String url =
'https://www.booths.co.uk/wp-content/uploads/British-Flower-1x1-2-660x371.jpg';
Widget _pic;
#override
void initState() {
_pic = Image.network(url);
super.initState();
}
_updateImgWidget() async {
setState(() {
_pic = CircularProgressIndicator();
});
Uint8List bytes = (await NetworkAssetBundle(Uri.parse(url)).load(url))
.buffer
.asUint8List();
setState(() {
_pic = Image.memory(bytes);
});
}
#override
Widget build(BuildContext context) {
return InkWell(
child: _pic,
onTap: () {
_updateImgWidget();
},
);
}
}
Another tricky solution is to add a dummy argument which changes every time, then the image will be treat as different image source and will refresh image every time when you access it. For example add t=currentTimestamp, but you don't need handle this argument in the web server.
ex: Image.network('https://picsum.photos/250?image=9?t=${DateTime.now().millisecond}'
Related
I simply made a flutter application that shows usage statistics of all installed application e.g if we spend two hours on WhatsApp my app show it,but the problem is : It also shows package names like
system ui, builder, launcher, com.package etc.
I am using this flutter package app_usage
Here is main.dart file
import 'package:flutter/material.dart';
import 'package:app_usage/app_usage.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<AppUsageInfo> _infos = [];
#override
void initState() {
super.initState();
}
void getUsageStats() async {
try {
DateTime endDate = new DateTime.now();
DateTime startDate = endDate.subtract(Duration(hours: 1));
List<AppUsageInfo> infoList =
await AppUsage.getAppUsage(startDate, endDate);
setState(() {
_infos = infoList;
});
for (var info in infoList) {
print(info.toString());
}
} on AppUsageException catch (exception) {
print(exception);
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('App Usage Example'),
backgroundColor: Colors.green,
),
body: ListView.builder(
itemCount: _infos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_infos[index].appName),
trailing: Text(_infos[index].usage.toString()));
}),
floatingActionButton: FloatingActionButton(
onPressed: getUsageStats, child: Icon(Icons.file_download)),
),
);
}
}
Here is my emulator output:
I found this code and modify it to switch between two pictures.
it seems to me that is ok but ..... iT doesn't work.
Can you help me to understand why?
Thanks.
See below the full code.
import 'dart:ui';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Image img; // variable named image to be named with the path
Image imgUp = Image.asset("assets/images/pressed.jpg"); //pressed button path
Image imgDown = Image.asset("assets/images/pressed.jpg"); //unpressed button path
#override
void initState() {super.initState();
img = imgUp; //inizialize the image as imgUp version
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Tap The Image!"),
centerTitle: true,
), //AppBar section ended
body: Center(child: GestureDetector(
child: img,
onTapDown: (tap) {
setState(() {
img = imgDown;
});
},
onTapUp: (tap) {
setState(() {
img = imgUp;
});
},
),
));
}
}
It probably doesn't work because you are using the same image for up and down. Please see here
Image imgUp = Image.asset("assets/images/pressed.jpg"); //pressed button path
Image imgDown = Image.asset("assets/images/pressed.jpg"); //
It works if you change the images, please see code below : [Note that the image changes back to imgUp as soon as you release the tap down.]
import 'dart:ui';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Image img; // variable named image to be named with the path
Image imgUp = Image.network("https://cdn3.iconfinder.com/data/icons/faticons/32/arrow-up-01-512.png"); //pressed button path
Image imgDown = Image.network("https://cdn3.iconfinder.com/data/icons/faticons/32/arrow-down-01-512.png"); //unpressed button path
#override
void initState() {super.initState();
img = imgUp; //inizialize the image as imgUp version
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Tap The Image!"),
centerTitle: true,
), //AppBar section ended
body: Center(child: GestureDetector(
child: img,
onTapDown: (tap) {
setState(() {
img = imgDown;
});
},
onTapUp: (tap) {
setState(() {
img = imgUp;
});
},
),
));
}
}
may I ask a way how to make this work.
I have a text file named questions.txt.
This file contains the following questions:
How old are you?
Where do you live?
What is your age?
I want to load these questions from the file, and render them as a list in Flutter.
questionnaires.dart
import 'package:flutter/material.dart';
class Questionnaires extends StatefulWidget {
#override
_QuestionnairesState createState() => _QuestionnairesState();
}
class _QuestionnairesState extends State<Questionnaires> {
String q1 = "";
String q2 = "";
String q3 = "";
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Center(
child: Column(
children: <Widget>[
Text(q1),
Text(q2),
Text(q3)
],
),
),
),
);
}
}
You can start with the most basic way of retrieving the questions from a .txt file using rootBundle.loadString, then display it using a ListView widget.
main.dart
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Questions',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyAppScreen(),
);
}
}
class MyAppScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppScreenState();
}
}
class MyAppScreenState extends State<MyAppScreen> {
List<String> _questions = [];
Future<List<String>> _loadQuestions() async {
List<String> questions = [];
await rootBundle.loadString('path/to/questions.txt').then((q) {
for (String i in LineSplitter().convert(q)) {
questions.add(i);
}
});
return questions;
}
#override
void initState() {
_setup();
super.initState();
}
_setup() async {
// Retrieve the questions (Processed in the background)
List<String> questions = await _loadQuestions();
// Notify the UI and display the questions
setState(() {
_questions = questions;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Flutter Questions")),
body: Center(
child: Container(
child: ListView.builder(
itemCount: _questions.length,
itemBuilder: (context, index) {
return Text(_questions[index]);
},
),
),
),
);
}
}
And here are the sample list of questions.
questions.txt
"How old are you?"
"Where do you live?"
"What is your age?"
In the example code above, you are parsing the text file line by line, please see LineSplitter. This is good for small and sample projects while you're testing out Flutter. But you should be able to update this implementation by following the official docs of Flutter, on how you can read from and write on files.
Furthermore, if you want to go big with your Flutter project, you should look into ways on how you can host your questions online, eg. served via REST APIs, then retrieve it using the http plugin for Flutter.
More on:
https://flutter.dev/docs/cookbook/networking/fetch-data
https://flutter.dev/docs/cookbook/persistence/reading-writing-files
https://pub.dev/packages/path_provider
Output:
I need to call from another widget the App class. How can I get a reference to the app class? I tried something like this:
static App myApp = this;
but a "this" is not defined, nor a "self".
Is there a way to make a "App" variable or put the app object into some kind of global variable?
EDIT:
To be more clear: I use a tabbed navigation style app and want to display a fullscreen spinning indicator (ModalProgressHud) that something is loading from the backend.
Now when I add the spinner code to some of the screens, the tabs will still be visible and clickable when the spinner is shown. Hence the idea to move the spinner code to the main app file, surrounding the tabbar creation.
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: 'My cool app',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: new App(),
);
}
}
Now in the App class, I initiate the tabs like this and wrap them in the build function inside the spinning indicators call ("ModalProgressHud"):
body: ModalProgressHUD(child: buildTabs(context), inAsyncCall: _saving, color: Colors.grey, opacity: 0.5),
import 'package:modal_progress_hud/modal_progress_hud.dart';
class App extends StatefulWidget {
#override
State<StatefulWidget> createState() => AppState();
}
class AppState extends State<App> {
bool _saving = false;
TabItem currentTab = TabItem.Dashboard;
Map<TabItem, GlobalKey<NavigatorState>> navigatorKeys = {
TabItem.Dashboard: GlobalKey<NavigatorState>(),
TabItem.Family: GlobalKey<NavigatorState>(),
TabItem.Groups: GlobalKey<NavigatorState>(),
TabItem.ShoppingList: GlobalKey<NavigatorState>(),
TabItem.Me: GlobalKey<NavigatorState>(),
};
AppState() {
_initManagers();
}
void _initManagers() {
new BackendManager();
new ShoppingListManager();
new UserManager();
}
void _selectTab(TabItem tabItem) {
UserManager user = new UserManager();
if (user.userIsLoggedIn()) {
// only if user is logged-in we allow to switch bottom navi tabs
setState(() {
currentTab = tabItem;
});
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async =>
!await navigatorKeys[currentTab].currentState.maybePop(),
child: Scaffold(
resizeToAvoidBottomPadding: false,
resizeToAvoidBottomInset: false,
// HERE THE WRAP OF THE MAIN TABS IN THE HUD WIDGET
body: ModalProgressHUD(child: buildTabs(context), inAsyncCall: _saving, color: Colors.grey, opacity: 0.5),
bottomNavigationBar: BottomNavigation(
currentTab: currentTab,
onSelectTab: _selectTab,
),
),
);
}
Widget buildTabs(BuildContext context) {
return Stack(children: <Widget>[
_buildOffstageNavigator(TabItem.Dashboard),
_buildOffstageNavigator(TabItem.Search),
_buildOffstageNavigator(TabItem.Shop),
_buildOffstageNavigator(TabItem.ShoppingList),
_buildOffstageNavigator(TabItem.Me),
]);
}
Widget _buildOffstageNavigator(TabItem tabItem) {
return Offstage(
offstage: currentTab != tabItem,
child: TabNavigator(
navigatorKey: navigatorKeys[tabItem],
tabItem: tabItem,
),
);
}
void _submit() {
setState(() {
_saving = true;
});
//Simulate a service call
print('>>>>>> submitting to backend...');
new Future.delayed(new Duration(seconds: 4), () {
setState(() {
_saving = false;
});
});
}
}
The ModalProgressHud is now in the app class. My problem is now, I want to set / call the ModalProgressHud from any other widget to show the fullscreen overlay spinning indicator.
Hence I was thinking if a global static variable works (and how do I set this?) or if there is any other way to call the submit() function inside the App class.
First, I wonder why you need to pass the reference of the app class?
If you want to pass the app instance or reference it to the child widget, you can create a widget class with a constructor accepting the App object.
NOTE: If you need this because you have to pass on some data, you might consider using provider or inheritedWidget + BloC.
This is just a rough example, if you can provide more details that might actually help, please do.
AnotherWidget.dart
class AnotherWidget extends StatelessWidget {
final MyApp myApp;
const AnotherWidget({Key key, this.myApp}) : super(key: key);
// Do whatever you want with myApp instance
}
MyApp.dart
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AnotherWidgetDemo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: AnotherWidget(myApp: this),
);
}
}
Hope this helps.
Try something like they do in Flutter for Web, import the file and use as, then you have the reference for calling a method
import 'package:hello_web/main.dart' as app;
main() async {
app.main();
}
I have an intro screen for my app, but it shows every time I open the app,
I need to show that for the 1st time only.
How to do that?
//THIS IS THE SCREEN COMES 1ST WHEN OPENING THE APP (SPLASHSCREEN)
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
//After 2seconds of time the Introscreen will e opened by bellow code
Timer(Duration(seconds: 2), () => MyNavigator.goToIntroscreen(context));
}
//The below code has the text to show for the spalshing screen
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Center(
child: Text('SPLASH SCREEN'),
),
);
}
}
Every time this screen opens the intro screen with 2 seconds delay.
but I want for the first time only How to do that with sharedpreference??
Please add the required code.
If you wish to show the intro screen only for the first time, you will need to save locally that this user has already seen intro.
For such thing you may use Shared Preference. There is a flutter package for Shared Preference which you can use
EDITED:
Please refer to the below complete tested code to understand how to use it:
import 'dart:async';
import 'package:after_layout/after_layout.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
color: Colors.blue,
home: new Splash(),
);
}
}
class Splash extends StatefulWidget {
#override
SplashState createState() => new SplashState();
}
class SplashState extends State<Splash> with AfterLayoutMixin<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 Home()));
} else {
await prefs.setBool('seen', true);
Navigator.of(context).pushReplacement(
new MaterialPageRoute(builder: (context) => new IntroScreen()));
}
}
#override
void afterFirstLayout(BuildContext context) => 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(
appBar: new AppBar(
title: new Text('IntroScreen'),
),
body: new Center(
child: new Text('This is the IntroScreen'),
),
);
}
}
Thanks to Ben B for noticing the incorrect use of delay in initState. I had used a delay because sometimes the context is not ready immediately inside initState.
So now I have replaced that with afterFirstLayout which is ready with the context. You will need to install the package after_layout.
I was able to do without using after_layout package and Mixins and instead I have used FutureBuilder.
class SplashState extends State<Splash> {
Future checkFirstSeen() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool _seen = (prefs.getBool('seen') ?? false);
if (_seen) {
return HomeScreen.id;
} else {
// Set the flag to true at the end of onboarding screen if everything is successfull and so I am commenting it out
// await prefs.setBool('seen', true);
return IntroScreen.id;
}
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: checkFirstSeen(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return MaterialApp(
initialRoute: snapshot.data,
routes: {
IntroScreen.id: (context) => IntroScreen(),
HomeScreen.id: (context) => HomeScreen(),
},
);
}
});
}
}
class HomeScreen extends StatelessWidget {
static String id = 'HomeScreen';
#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 {
static String id = 'IntroScreen';
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('IntroScreen'),
),
body: new Center(
child: new Text('This is the IntroScreen'),
),
);
}
}
I always try to use minimum count of packages, because in future it can conflict with ios or android. So my simple solution without any package:
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
final splashDelay = 2;
#override
void initState() {
super.initState();
_loadWidget();
}
_loadWidget() async {
var _duration = Duration(seconds: splashDelay);
return Timer(_duration, checkFirstSeen);
}
Future checkFirstSeen() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool _introSeen = (prefs.getBool('intro_seen') ?? false);
Navigator.pop(context);
if (_introSeen) {
Navigator.pushNamed(context, Routing.HomeViewRoute);
} else {
await prefs.setBool('intro_seen', true);
Navigator.pushNamed(context, Routing.IntroViewRoute);
}
}
#override
Widget build(BuildContext context) {
//your splash screen code
}
}
Use shared_preferences:
Full code:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
var prefs = await SharedPreferences.getInstance();
var boolKey = 'isFirstTime';
var isFirstTime = prefs.getBool(boolKey) ?? true;
runApp(MaterialApp(home: isFirstTime ? IntroScreen(prefs, boolKey) : RegularScreen()));
}
class IntroScreen extends StatelessWidget {
final SharedPreferences prefs;
final String boolKey;
IntroScreen(this.prefs, this.boolKey);
Widget build(BuildContext context) {
prefs.setBool(boolKey, false); // You might want to save this on a callback.
return Scaffold();
}
}
class RegularScreen extends StatelessWidget {
Widget build(BuildContext context) => Scaffold();
}
I just had to do exactly the same thing, here's how I did it:
First, in my main method, I open the normal main page and the tutorial:
MaterialApp(
title: 'myApp',
onGenerateInitialRoutes: (_) => [MaterialPageRoute(builder: mainPageRoute), MaterialPageRoute(builder: tutorialSliderRoute)],
)
...and then I use a FutureBuilder to build the tutorial only if necessary:
var tutorialSliderRoute = (context) => FutureBuilder(
future: Provider.of<UserConfiguration>(context, listen: false).loadShowTutorial() // does a lookup using Shared Preferences
.timeout(Duration(seconds: 3), onTimeout: () => false),
initialData: null,
builder: (context, snapshot){
if (snapshot.data == null){
return CircularProgressIndicator(); // This is displayed for up to 3 seconds, in case data loading doesn't return for some reason...
} else if (snapshot.data == true){
return TutorialSlider(); // The Tutorial, implemented using IntroSlider()
} else {
// In case the tutorial shouldn't be shown, just return an empty Container and immediately pop it again so that the app's main page becomes visible.
SchedulerBinding.instance.addPostFrameCallback((_){Navigator.of(context).pop();});
return Container(width: 0, height: 0);
}
},
);
Also, I think the tutorial should be shown again in case the user does not finish it, so I set only set the variable showTutorial to false once the user has completed (or skipped) the tutorial:
class TutorialSlider extends StatefulWidget {
#override
State<StatefulWidget> createState() => TutorialSliderState();
}
class TutorialSliderState extends State<TutorialSlider> {
...
#override
Widget build(BuildContext context) => IntroSlider(
...
onDonePress: (){
Provider.of<UserConfiguration>(context, listen: false).setShowTutorial(false);
Navigator.of(context).pop();
}
);
}
I took a different approach. I agree with the other answers that you should save your isFirstRun status via SharedPreferences. The tricky part then is how to show the correct widget in such a way that when you hit back you close out of the app correctly, etc. I first tried doing this by launching a my SplashWidget while building my HomePageWidget, but this turned out to lead to some weird Navigator errors.
Instead, I wound up calling runApp() multiple times with my different widget as appropriate. When I need to close the SplashWidget, rather than pop it, I just call runApp() again, this time with my HomePageWidget as the child property. It is safe to call runApp() multiple times according to this issue, indeed even for splash screens.
So it looks something like this (simplified obviously):
Future<void> main() async {
bool needsFirstRun = await retrieveNeedsFirstRunFromPrefs();
if (needsFirstRun) {
// This is will probably be an async method but no need to
// delay the first widget.
saveFirstRunSeen();
runApp(child: SplashScreenWidget(isFirstRun: true));
} else {
runApp(child: HomePageWidget());
}
}
I have an isFirstRun property on SplashScreenWidget because I can launch it in two ways--once as a true splash screen, and once from settings so that users can see it again if they want. I then inspect that in SplashScreenWidget to determine how I should return to the app.
class SplashScreenWidget extends StatefulWidget {
final bool isFirstRun;
// <snip> the constructor and getState()
}
class _SplashScreenWidgetState extends State<SplashScreenWidget> {
// This is invoked either by a 'skip' button or by completing the
// splash screen experience. If they just hit back, they'll be
// kicked out of the app (which seems like the correct behavior
// to me), but if you wanted to prevent that you could build a
// WillPopScope widget that instead launches the home screen if
// you want to make sure they always see it.
void dismissSplashScreen(BuildContext ctx) {
if (widget.isFirstRun) {
// Then we can't just Navigator.pop, because that will leave
// the user with nothing to go back to. Instead, we will
// call runApp() again, setting the base app widget to be
// our home screen.
runApp(child: HomePageWidget());
} else {
// It was launched via a MaterialRoute elsewhere in the
// app. We want the dismissal to just return them to where
// they were before.
Navigator.of(ctx).pop();
}
}
}