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.
Related
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.
I have a Home Screen Widget, that plays a fullscreen background video using the video_player package.
This code works fine for me:
class HomeScreen extends StatefulWidget {
HomeScreen({Key key}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
VideoPlayerController _controller;
void initState() {
super.initState();
// Pointing the video controller to mylocal asset.
_controller = VideoPlayerController.asset("assets/waterfall.mp4");
_controller.initialize().then((_) {
// Once the video has been loaded we play the video and set looping to true.
_controller.play();
_controller.setLooping(true);
// Ensure the first frame is shown after the video is initialized.
setState(() {});
});
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Stack(
children: <Widget>[
SizedBox.expand(
child: FittedBox(
// If your background video doesn't look right, try changing the BoxFit property.
// BoxFit.fill created the look I was going for.
fit: BoxFit.fill,
child: SizedBox(
width: _controller.value.size?.width ?? 0,
height: _controller.value.size?.height ?? 0,
child: VideoPlayer(_controller),
),
),
),
Container(
child: Center(
child: Text('Hello!'),
),
),
],
),
),
);
}
}
The question is, how can I implement this using flutter Hooks? I understand that I have to use useEffect() to implement the functionality of initState() and dispose(), useFuture() and maybe useMemoized() to handle asynchronous _controller.initialize() call and what possibly else? But, I cannot glue them to get the desired result. Can anyone indicate to me the "using Hooks" implementation of the above code?
I was looking for the answer to how to convert a VideoPlayer demo from StatefulWidget to HookWidget when I came across this question. I've come up with something that works so I'll post it here since there is nothing elsewhere that I could find and some others are hitting this page looking for an answer.
I used a viewmodel. The video controller is a property of the viewmodel. This code will not compile since some of the controls are not included. But it will demonstrate the structure and incorporation of the viewmodel.
Here's the widget file:
import 'package:flutter/foundation.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:video_player/video_player.dart';
import 'intro_viewmodel.dart';
class IntroPage extends HookWidget {
Future<void> saveAndGetStarted(BuildContext context) async {
final IntroViewModel introViewModel = context.read(introViewModelProvider);
await introViewModel.completeIntro();
}
Future<void> onNext(BuildContext context) async {
final IntroViewModel introViewModel = context.read(introViewModelProvider);
await introViewModel.incrementIntro();
}
final List<SliderModel> slides = [
SliderModel(
description: 'A word with you before you get started.\n',
title: 'Why This App?',
localImageSrc: 'media/Screen1-Movingforward-pana.svg',
backgroundColor: Colors.lightGray),
SliderModel(
description: 'This information will help the app be more accurate\n',
title: 'Personal Profile',
localImageSrc: 'media/Screen2-Teaching-cuate.svg',
backgroundColor: Colors.lightGray)
];
#override
Widget build(BuildContext context) {
final IntroViewModel introViewModel = context.read(introViewModelProvider);
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Column(
children: [
Text(
slides[introViewModel.index].description,
style: Theme.of(context).textTheme.headline5,
textAlign: TextAlign.center,
),
Expanded(
child: FractionallySizedBox(
widthFactor: .98,
heightFactor: .5,
child: VideoPlayer(introViewModel.videoController),
)),
Align(
alignment: Alignment.bottomCenter,
child: CustomRaisedButton(
onPressed: () {
if (introViewModel.index == slides.length - 1) {
saveAndGetStarted(context);
} else {
onNext(context);
}
},
color: Theme.of(context).accentColor,
borderRadius: 15,
height: 50,
child: Text(
introViewModel.index == 0
? 'Continue'
: 'Save and Get Started',
style: Theme.of(context)
.textTheme
.headline5
.copyWith(color: Colors.white),
),
),
),
],
),
),
));
}
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IterableProperty<SliderModel>('slides', slides));
}
}
And here is the viewmodel code
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:video_player/video_player.dart';
import '../top_level_providers.dart';
final introViewModelProvider = ChangeNotifierProvider<IntroViewModel>((ref) {
//this singleton class provides global access to selected variables
final SharedPreferencesService localSharedPreferencesService =
ref.watch(sharedPreferencesService);
return IntroViewModel(localSharedPreferencesService);
});
class IntroViewModel extends ChangeNotifier {
IntroViewModel(this.localSharedPreferencesService) : super() {
state = localSharedPreferencesService?.isIntroComplete();
// Pointing the video controller to my local asset.
videoController = VideoPlayerController.asset('media/test_search.mp4');
videoController.initialize().then((_) {
// Once the video has been loaded we play the video and set looping to true.
// not autoplaying yet
// videoController.play();
// videoController.setLooping(true);
});
}
final SharedPreferencesService localSharedPreferencesService;
VideoPlayerController videoController;
bool state = false;
int index = 0;
Future<void> completeIntro() async {
await localSharedPreferencesService.setIntroComplete();
state = true;
notifyListeners();
}
Future<void> incrementIntro() async {
++index;
notifyListeners();
}
bool get isIntroComplete => state;
}
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),
),
);
}
}
I am getting posts from a wordpress website into a flutter app.
Each post has 3 pictures and this is the layout of the images
What I am trying to do is when I Tap on IMAGE 1 or IMAGE 2 or IMAGE 3, that image will be shown on MAIN IMAGE.
Does it makes sense?
i Hope That Below Code help you. in following code i used network image but you can also used assert images to.
import 'package:flutter/material.dart';
void main() => runApp(new Demo());
class Demo extends StatefulWidget {
#override
_DemoState createState() => _DemoState();
}
class _DemoState extends State<Demo> with TickerProviderStateMixin {
String image1 = "http://via.placeholder.com/350x150";
String image2 = "http://via.placeholder.com/200x150";
String image3 = "http://via.placeholder.com/200x150";
String currentMainImage = "http://via.placeholder.com/350x150" ;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: new Text("table demo"),
),
body: new Container(
child: new Column(
children: <Widget>[
Container(
height:150.0,
child: new Image.network(currentMainImage,fit: BoxFit.fill,)
),
new Row(
children: <Widget>[
Expanded(
child: InkWell(
onTap : (){
setState(() {
currentMainImage = image1;
});
},
child: new Image.network(image1)
)
),
Expanded(
child: InkWell(
onTap : (){
setState(() {
currentMainImage = image2;
});
},
child: new Image.network(image2)
)
),
Expanded(
child: InkWell(
onTap : (){
setState(() {
currentMainImage = image3;
});
},
child: new Image.network(image3)
)
),
],
)
],
)
)
)
);
}
}
You need to use a stateful widget
You may use Column and Row to achieve that layout.
And Image to display the images within the layout.
You may specifically use Image.network(url) constructor to show images from your word press site.
The image1, image2 and image3 can be wrapped in a InkWell widget. And InkWell's onTap can have code to update the url/image of the Main Image widget within a setState()
Hope that helps!
New to flutter. Working on a personal project. Stuck with a small issue related to show images. Here is my widget code which I'm using for showing images.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cached_network_image/cached_network_image.dart';
class UserProfile extends StatefulWidget {
#override
UserProfileState createState() => new UserProfileState();
}
class UserProfileState extends State<UserProfile> {
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Map userDetails = {};
String profileImgPath;
#override
void initState() {
super.initState();
getUserDetails();
}
Future<Null> getUserDetails() async {
try {
final SharedPreferences prefs = await _prefs;
this.userDetails = json.decode(prefs.getString('user'));
if (prefs.getString('user') != null) {
if (this.userDetails['isLoggedIn']) {
setState(() {
this.profileImgPath = this.userDetails['profileImg'];
print('Shared preference userDetailsss : ${this.userDetails}');
});
}
} else {
print('Shared preference has no data');
}
} catch (e) {
print('Exception caught at getUserDetails method');
print(e.toString());
}
}
#override
Widget build(BuildContext context) {
Widget profileImage = new Container(
margin: const EdgeInsets.only(top: 20.0),
child: new Row(
children: <Widget>[
new Expanded(
child: new Column(
children: <Widget>[
new CircleAvatar(
backgroundImage: (this.profileImgPath == null) ? new AssetImage('images/user-avatar.png') : new CachedNetworkImageProvider(this.profileImgPath),
radius:50.0,
)
],
)
)
],
)
);
return new Scaffold(
appBar: new AppBar(title: new Text("Profile"), backgroundColor: const Color(0xFF009688)),
body: new ListView(
children: <Widget>[
profileImage,
],
),
);
}
}
What I'm trying to do is, show the default user-avatar.png image as long as CachedNetworkImageProvider don't get original image. But, it's bit behaving differently.
Whenever I'm opening the page - I'm getting a blank blue box then suddenly the original image from CachedNetworkImageProvider comes up.
Can't able to understand what's happening.
#Jonah Williams for your reference -
CachedNetworkImage can't be used for backgroundImage property because it does not extends ImageProvider. You can create a custom CircleAvatar like described below to use the CachedNetworkImage package:
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
class CustomCircleAvatar extends StatelessWidget {
final int animationDuration;
final double radius;
final String imagePath;
const CustomCircleAvatar({
Key key,
this.animationDuration,
this.radius,
this.imagePath
}) : super(key: key);
#override
Widget build(BuildContext context) {
return new AnimatedContainer(
duration: new Duration(
milliseconds: animationDuration,
),
constraints: new BoxConstraints(
minHeight: radius,
maxHeight: radius,
minWidth: radius,
maxWidth: radius,
),
child: new ClipOval(
child: new CachedNetworkImage(
errorWidget: (context, url, error) => Icon(Icons.error),
fit: BoxFit.cover,
imageUrl: imagePath,
placeholder: (context, url) => CircularProgressIndicator(),
),
),
);
}
}
And how to use:
body: new Center(
child: new CustomCircleAvatar(
animationDuration: 300,
radius: 100.0,
imagePath: 'https://avatars-01.gitter.im/g/u/mi6friend4all_twitter?s=128',
),
),
Maybe it is not the better way. But, it works!
(I'm assuming that CachedNetworkImageProvider is actually CachedNetworkImage from this package).
This line of code will always display the second image.
(this.profileImgPath == null)
? new AssetImage('images/user-avatar.png')
: new CachedNetworkImageProvider(this.profileImgPath)
Since profileImagePath is not null, the AssetImage is never created. Even if it was, as soon as it's not the cached network image will replace it before it has loaded. Instead, use the placeholder parameter of the network image. This will display your asset image until the network image loads.
new CachedNetworkImage(
placeholder: new AssetImage('images/user-avatar.png'),
imageUrl: profileImgPath,
)