Adding images with persistent storage - flutter

I am building a home page where the user can replace default image. Whenever the user opens the application second time, I want that image user picked previously to show up and not the default image. How do I do this in dart/flutter?
I tried doing research, but was not able to find helpful articles.

You can save the image as files of specific path(getting the directory from path_provider, and then look for file at the same path the next time.
Also you can use image_picker to select the image on iOS and Android.
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
late Directory dir;
late File file;
final imagePicker = ImagePicker();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
dir = await getApplicationDocumentsDirectory();
file = File("${dir.path}/userImage1");
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(
height: 300,
width: 300,
child: MyWidget(),
),
),
),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
#override
Widget build(BuildContext context) {
if (!file.existsSync()) {
return Column(
children: [
Container(
color: Colors.purple,
),
ElevatedButton(
onPressed: () async {
final imgfile = await imagePicker.pickImage(source: ImageSource.gallery);
if (imgfile != null) {
await imgfile.saveTo(file.path);
//clear the cache image of that path, or else the image won't change
FileImage(file).evict();
}
setState(() {});
},
child: const Text("Pick Image"),
),
],
);
} else {
return Column(
children: [
Image.file(key: UniqueKey(), file),
ElevatedButton(
onPressed: () async {
final imgfile = await imagePicker.pickImage(source: ImageSource.gallery);
if (imgfile != null) {
await imgfile.saveTo(file.path);
//clear the cache image of that path, or else the image won't change
FileImage(file).evict();
}
setState(() {});
},
child: const Text("Pick Image")),
],
);
}
}
}

You will need to persist between runs which image the user has selected. To do so, you can use the shared_preferences package.
When the user selects an image, set the image and also set a key using shared_preferences indicating which image was selected. The value of the key will likely be a string, and it could be -- for example -- a URL, a path on the local filesystem, or a path in the assets bundle. How exactly you choose to represent the user's selection is up to you and depends on your specific use case.
And, when your app loads, retrieve that key from shared_preferences and use the value to load the correct image for display.

For this, it is necessary to save the last remaining image using the internal memory.
For example
Getx Storage
Hive
Remember the image sequence number every time the image is changed. This will help you show the images from the index immediately when the program is closed and reopened.
I gave a brief explanation here as general information.

Related

'Screenshot' package is not saving the image in flutter web

I'm trying to save a picture of widget which contains a image and a few texts with it.
I tried the screenshot package and it is working perfectly fine with the Text widgets and a few others as well, but when I try to put a image inside it and save the screenshot saves the blank image with no image in it.
Here is the code, and to be clear I'm not trying to save this image only which is already in my assets but with a few other widgets around it.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:screenshot/screenshot.dart';
import 'package:stationary/utils/app_images.dart';
import 'card_screen/components/save_image.dart';
class TestScreen extends StatefulWidget {
const TestScreen({super.key});
#override
State<TestScreen> createState() => _TestScreenState();
}
class _TestScreenState extends State<TestScreen> {
Uint8List? _imageFile;
ScreenshotController screenshotController = ScreenshotController();
#override
Widget build(BuildContext context) {
print("buildt");
return Scaffold(
body: Center(
child: Screenshot(
controller: screenshotController,
child: Column(
children: [
Text("Header", style: Theme.of(context).textTheme.headline3),
SizedBox(
child: GestureDetector(
onTap: () {
screenshotController.capture().then((image) {
//Capture Done
setState(() {
_imageFile = image!;
print(_imageFile == null);
});
saveInBrowser(image!);
}).catchError((onError) {
print(onError);
});
},
child: Container(
width: 300.0,
height: 500.0,
child: Image.asset('assets/images/clean-code.png',
fit: BoxFit.cover)),
),
),
Text("I am subtitle place holder",
style: Theme.of(context).textTheme.subtitle1),
],
),
)),
);
}
}
Save In Browser function
void saveInBrowser(Uint8List bytes) {
final dataUri = 'data:text/plain;base64,${base64.encode(bytes)}';
html.document.createElement('a') as html.AnchorElement
..href = dataUri
..download = 'image.png'
..dispatchEvent(html.Event.eventType('MouseEvent', 'click'));
}
I was trying to save the image and texts as the screenshot but got only the widgets and when tried to work with images only got the blank.
You can use image_downloader_web package like this:
Future<void> saveInBrowser(Uint8List bytes) async{
final WebImageDownloader _webImageDownloader = WebImageDownloader();
await _webImageDownloader.downloadImageFromUInt8List(uInt8List: bytes);
}
and also you need to run it in release mode according to this open github issue, run this in terminal:
flutter run -d web-server --web-port 3344 --release --web-renderer canvaskit --dart-define=BROWSER_IMAGE_DECODING_ENABLED=false
also remember to build it with this config too.

Flutter Image.memory() loading animation with slow large files

I am fetching an image from the gallery and displaying it in my cropper with Image.memory(). But sometimes (with large files) it takes a few seconds for the image to show. I want to show a loading animation but how do I detect when it is loading or finished? There is no loadingBuilder like for Image.network(). Any ideas?
final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
Image.memory(_imageToCrop!)
Like Ankit Kumar Maurya said I studied the frameBuilder documentation and this solved my issue:
Image.memory(
_imageToCrop!,
frameBuilder:
((context, child, frame, wasSynchronouslyLoaded) {
if (wasSynchronouslyLoaded) return child;
return AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: frame != null
? child
: SizedBox(
height: 60,
width: 60,
child: CircularProgressIndicator(
strokeWidth: 6),
),
);
}),
),
you can maintain an enum for this,
enum State {idle, loading, done ,error}
now,
State fetchState = State.idle
void fetch()async{
setState((){
fetchState = State.loading;
});
try{
final XFile? image = await _picker.pickImage(source:
ImageSource.gallery);
Image.memory(_imageToCrop!);
setState((){
fetchState = State.done;
});
}catch(e){
setState((){
fetchState = State.error;
});
}
}
Now use this states in your widget tree to show loader when state is loading.
you can use a single boolean variable as said in other answer but with this method you can catch errors at run time. You can use this method for api calls also.
You should once check this documentation provided by flutter frame_builder. You could also use frameBuilder for your requirement.
May be my code will help you:-
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
void main() {
runApp(const MaterialApp(
title: 'Temp',
home: const MyApp(),
));
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool loading = false;
late Uint8List image;
XFile? file = null;
ImagePicker imagePicker = ImagePicker();
Future<void> pickImage() async {
print('starting');
setState(() {
loading = true;
});
file = await imagePicker.pickImage(source: ImageSource.gallery);
print('file');
if(file != null){
print(file.toString());
image = await file!.readAsBytes();
}
setState(() {
loading = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Image Picker'),
),
body: loading
? Container(
child: const Center(child: CircularProgressIndicator()),
)
: file != null?Container(child: Center(child: Image.memory(image),)) : Container(
child: const Center(child: Text('Pick an Image')),
),
floatingActionButton: FloatingActionButton(
child: const Icon(
Icons.image,
),
onPressed: (){
pickImage();
}),
);
}
}
Short Description - my code will fetch an image from the system and then show … I have used a loading widget while the image is fetching and being converted to bytes.
It might be helpful if you share your logic here to figure out the main problem(if missing any thing).

How to Force the user to answer a question for open the app in Flutter?

I have a Flutter application and I want to add a page that appears when opening the application asking the user to answer a question such as how many countries in the world - the answer is already stored in the application, so that if the answer is correct, the answer is stored and the application opens and this page does not appear again,
But if the answer is wrong, the user remains On this page, he cannot open the application until he writes the correct answer
Any suggestions or examples that would be helpful?
Update: I have created the following verification page that checks if the entered text is equal to the stored text,I used flutter_secure_storage to store the text if it is true Now iwant to know how i can add the shared_preferences to my code?
class check extends StatefulWidget {
#override
_checkState createState() => _checkState();
}
class _checkState extends State<check> {
final formKey = GlobalKey<FormState>();
final verifierController = TextEditingController();
String storedvalue = '200';
#override
void initState() {
super.initState();
init();
}
Future init() async {
final realcode = await UserSecureStorage.getCodestored() ?? '';
setState(() {
this.verifierController.text = realcode;
});
}
Codecheck() async {
await UserSecureStorage.setUsername(verifierController.text);
if (storedvalue == verifierController.text) {
Navigator.of(context).pushReplacementNamed('/homeScreen');
}
else {
Navigator.of(context).pushReplacementNamed('/checkScreen');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(10),
child: Center(
child: Stack(
children: [
Align(
child: Text(
'how many countries are there in the world?',
.........
),
Align(
child: TextFormField(
.......
controller: verifierController,
)),
Align(
child: RaisedButton(
.........
onPressed: () async {
Codecheck();
},
..........
you would check the user's answer, if it's correct, you save a boolean in the shared preferences and then navigate to the app home page and every time you open the app you check on this boolean from the shared preferences, if it's true then don't show the question and open the home page directly, if not, then show the question again
In a very simple way, you can use the SharedPreferences plugin to store the answer to your question permanently, for example
You can store a "question" key that will have the value "how many countries are there in the world?" (optional).
You also store an "answer" key with the value "324" (the exact number of countries in the world)
Then you create an "answer_found" key which will be a boolean and will update if yes or no the user answers the question correctly.
Then when the application starts, you will first query the "answer_found" key to see if its value is True.
If this value is True, you do not display the questionnaire page, if it is false or null, you display the questionnaire page.
When the user will enter the answer, simply compare there his answer to the one contained in the "answer" key in the preferences. If it is correct, simply update the key "answer_found" to become true. In the opposite case do nothing (or what you want)
UPDATE :
As you asked, here is an extract of the code.
I made it as simple as possible (although it is a bit barbaric) so that you can understand the mechanism as well as possible and that you can adapt it as you want.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await SharedPreferences.getInstance().then((preferences) async {
//Optional key (and can put want you want)
//Store the question in preferences
await preferences.setString('question', 'how many countries are there in the world?');
//Store the answer
await preferences.setInt('answer', 324);
});
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool? questionAnswered;
#override
void initState() {
super.initState();
_getQuestionState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: home,
);
}
Widget get home {
if (questionAnswered == null) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
} else if (questionAnswered!) {
return const HomePage();
} else {
return const QuestionPage();
}
}
Future<void> _getQuestionState() async {
final preferences = await SharedPreferences.getInstance();
//obtaining the present value of 'answer_found' to know if the question has been answered (with a correct answer)
final isQuestionAnswered = preferences.getBool('answer_found') ??
false; //if the value is null, we set it to false (to avoid a NullException)
setState(() => questionAnswered = isQuestionAnswered);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('Home Page'),
),
body: const Center(
child: Text('WELCOME'),
),
);
}
class QuestionPage extends StatefulWidget {
const QuestionPage({Key? key}) : super(key: key);
#override
State<QuestionPage> createState() => _QuestionPageState();
}
class _QuestionPageState extends State<QuestionPage> {
late final TextEditingController _answerTextController;
#override
void initState() {
super.initState();
_answerTextController = TextEditingController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const Text('how many countries are there in the world?'),
TextField(
controller: _answerTextController,
decoration: const InputDecoration(hintText: 'Enter answer'),
),
ElevatedButton(
onPressed: () async {
//Convert the user's response to be in an integer type (because we want to make a comparison with an integer)
//The user's response will be null, if the user has not entered an integer
final userAnswer = int.tryParse(_answerTextController.text);
if (userAnswer != null) {
final preferences = await SharedPreferences.getInstance();
final storedAnswer = preferences.getInt('answer')!;
if (userAnswer == storedAnswer) {
preferences.setBool('answer_found', true);
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const HomePage()));
} else {
//Notify user that his answer was wrong or do some stuff (what you want)
}
} else {
//Do some stuff
}
},
child: const Text('Validate')),
],
),
),
);
}
}
I have never used the flutter_secure_storage plugin, but following the code snippet I made for you, I think you could readapt it to work with flutter_secure storage, the reasoning and logic is the same.
P.S: You don't have to use shared_preferences and flutter_secure_storage at the same time. You can simply use flutter_secure_storage which, unlike shared_preferences, offers you a secure storage space (as its name indicates) and you only have to implement the same logic.
I hope you will have the splash screen as the first screen to load in your application.
now, when the first screen will load.
make this first screen a stateful.
this will allow you to use the "initstate" method. and you know that the initstate method will be called first to execute the code.
initstate(){
/// in this case I am using "get_storage" to store the data in local.
GetStorage prefs = GetStorage("ApplicationName");
bool isAnswered = prefs.read("isAnsweredQuestion") ?? false;
if(isAnswered){
/// redirect to the other screen.
}else{
/// redirect to the screen where you have the questions
/// or open the dialog having questions.
}
super.initstate();
};
the initstate method will execute for the first time when the application loads and execute the splash screen and it will check for the local data store.
if the user is opening the application for the first time then the local data will be null and we have used the null-check operator to handle that.
If the user already answered the question then we will get "true" stored in the local. and we will redirect the user to the other screen in that case.

Share local image with Flutter

I want to share an image that I took from the CameraController.
I location of the file is as example /data/user/0/com.user.test/cache/2019-09-10 16:32:52.281842.png
How it is possible to share this local image?
I added these two line for read/write to local storage:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
I use the share component from https://pub.dev/packages/esys_flutter_share which works great.
void _sharePicture() async {
print('Share picture');
print(this.imagePath);
final ByteData bytes = await rootBundle.load(this.imagePath);
await Share.file('esys image', 'esys.png', bytes.buffer.asUint8List(), 'image/png', text: 'My optional text.');
}
this.imagePath is the local location of the file: :/data/user/0/com.user.test/cache/2019-09-10 16:32:52.281842.png
Do you first have to save the image? And the use it for sharing? How is it possible to share this local image?
The idea is share Uint8List
This demo use camera_camera package's example. https://github.com/gabuldev/camera_camera/tree/master/example
camera_camera package https://pub.dev/packages/camera_camera is an greate package have well made features and use camera plugin inside
code snippet
after click take picture, the system return a file (val in this example), read bytes and transfer to Uint8List
print("path ${val}");
List<int> bytes = await val.readAsBytes();
Uint8List ubytes = Uint8List.fromList(bytes);
await Share.file('ESYS AMLOG', 'amlog.jpg', ubytes, 'image/jpg');
full code
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:camera_camera/camera_camera.dart';
import 'dart:typed_data';
import 'package:esys_flutter_share/esys_flutter_share.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 Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
File val;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Rully")),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.camera_alt),
onPressed: () async {
val = await showDialog(
context: context,
builder: (context) => Camera(
mode: CameraMode.fullscreen,
orientationEnablePhoto: CameraOrientation.landscape,
/*
imageMask: CameraFocus.square(
color: Colors.black.withOpacity(0.5),
),
*/
));
print("path ${val}");
List<int> bytes = await val.readAsBytes();
Uint8List ubytes = Uint8List.fromList(bytes);
await Share.file('ESYS AMLOG', 'amlog.jpg', ubytes, 'image/jpg');
setState(() {});
}),
body: Center(
child: Container(
height: MediaQuery.of(context).size.height * 0.7,
width: MediaQuery.of(context).size.width * 0.8,
child: val != null
? Image.file(
val,
fit: BoxFit.contain,
)
: Text("Tire a foto"))));
}
}
demo screen
In camera_camera example take picture button will show in landscape mdoe
file path display in bottom
For camera plugin official example, I only change the following
code snippet
void onTakePictureButtonPressed() {
takePicture().then((String filePath) async{
if (mounted) {
setState(() {
imagePath = filePath;
videoController?.dispose();
videoController = null;
});
if (filePath != null) {
showInSnackBar('Picture saved to $filePath');
File val = File(filePath);
List<int> bytes = await val.readAsBytes();
Uint8List ubytes = Uint8List.fromList(bytes);
await Share.file('ESYS AMLOG', 'amlog.jpg', ubytes, 'image/jpg');
}
}
});
}

How to control gif animation in Flutter?

I'm trying to restart an animated gif on Flutter. The gif image loads from network without a problem and animates after loading. I need to restart the animation on tapping a button.
Tried so far:
- setState
- change Key to some other unique key and setState to rebuild.
Solution as #chemamolins 's suggestion:
int _robotReloadCount=0;
....
GestureDetector(
onTap: () {
onTapRobot();
},
child: Center(
child: Container(
margin: EdgeInsets.only(top: 55.0, bottom: 5.0),
height: 150.0,
width: 150.0,
child:
FadeInImage(
key: this._robotImageKey,
placeholder: AssetImage('assets/common/robot_placeholder.png'),
image: NetworkImage(snapshot.data['robot_image_path'] +"robot_level" +snapshot.data['robot_level'].toString() +".gif"+"?"+this._robotReloadCount.toString()))),
),
),
....
onTapRobot() async{
setState(() {
this._robotReloadCount++;
});
}
I have done a lot of tests and it is not easy. The image is cached by the 'ImageProvider' and whatever you change or no matter the times you invoke build() the image is loaded from what is available in the cache.
So, apparently, you only have two options.
Either you rebuild with a new url, for instance by appending #whatever to the image url.
Or you remove the image from the cache as shown in the code below.
In either case you need to fetch again the image from the network.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String url = "https://media.giphy.com/media/hIfDZ869b7EHu/giphy.gif";
void _evictImage() {
final NetworkImage provider = NetworkImage(url);
provider.evict().then<void>((bool success) {
if (success) debugPrint('removed image!');
});
setState(() {});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: Image.network(url),
),
floatingActionButton: new FloatingActionButton(
onPressed: _evictImage,
child: new Icon(Icons.remove),
),
);
}
}