I'm new to the Bloc pattern so I'm having a bit of trouble with updating an image that is chosen with image_picker.
This is my code so far
image_picker_provider.dart
import 'dart:io';
import 'package:image_picker/image_picker.dart';
class ImagePickerProvider {
Future<File> getImage() async {
File image = await ImagePicker.pickImage(
source: ImageSource.gallery, maxWidth: 150.0, maxHeight: 150.0);
return image;
}
Future<File> takeImage() async {
var image = await ImagePicker.pickImage(
source: ImageSource.camera, maxWidth: 150.0, maxHeight: 150.0);
return image;
}
}
repository.dart
Future<File> getImage() => _imagePickerProvider.getImage();
Future<File> takeImage() => _imagePickerProvider.takeImage();
form.dart
Widget picture() {
return StreamBuilder(
stream: _bloc.profilePicture,
builder: (BuildContext context, AsyncSnapshot<Future<File>> snapshot) {
if (!snapshot.hasData || snapshot.hasError) {
return defaultPicture();
} else {
return profilePicture(snapshot);
}
});
}
Widget defaultPicture() {
return Container(
width: 150.0,
height: 150.0,
decoration: new BoxDecoration(
image: new DecorationImage(
image: AssetImage("assets/defaultProfile.jpg"),
fit: BoxFit.cover,
),
borderRadius: new BorderRadius.all(new Radius.circular(128.0)),
border: new Border.all(
color: Colors.black,
width: 1.0,
),
),
);
}
Widget profilePicture(File snapshotData) {
return Container(
width: 150.0,
height: 150.0,
decoration: new BoxDecoration(
image: new DecorationImage(
image: FileImage(snapshotData),
fit: BoxFit.cover,
),
borderRadius: new BorderRadius.all(new Radius.circular(128.0)),
border: new Border.all(
color: Colors.black,
width: 1.0,
),
),
);
}
And so far all I have regarding the profile picture inside of my
create_account_bloc.dart
import 'dart:async';
import 'dart:io';
import 'package:rxdart/rxdart.dart';
import '../resources/repository.dart';
class CreateAccountBloc {
final _repository = Repository();
final _email = BehaviorSubject<String>();
final _password = BehaviorSubject<String>();
final _firstName = BehaviorSubject<String>();
final _phoneNumber = BehaviorSubject<int>();
final _profilePicture = BehaviorSubject<Future<File>>();
final _profilePictureURL = BehaviorSubject<String>();
final _createAccountStatus = BehaviorSubject<CreateAccountStatus>();
Observable<String> get email => _email.stream.transform(_validateEmail);
Observable<String> get password =>
_password.stream.transform(_validatePassword);
Observable<String> get firstName =>
_firstName.stream.transform(_validateFirstName);
Observable<CreateAccountStatus> get createAccountStatus =>
_createAccountStatus.stream;
Observable<Future<File>> get profilePicture => _profilePicture.stream;
String get emailAddress => _email.value;
// Change data
Function(String) get changeEmail => _email.sink.add;
Function(String) get changePassword => _password.sink.add;
Function(String) get changeFirstName {
return _firstName.sink.add;
}
Function(CreateAccountStatus) get changeAccountStatus =>
_createAccountStatus.sink.add;
final _validateEmail =
StreamTransformer<String, String>.fromHandlers(handleData: (email, sink) {
if (Validator.isValidEmail(email)) {
sink.add(email);
} else {
sink.addError(StringConstant.emailValidateMessage);
}
});
final _validatePassword = StreamTransformer<String, String>.fromHandlers(
handleData: (password, sink) {
if (password.length >= 6) {
sink.add(password);
} else {
sink.addError(StringConstant.passwordValidateMessage);
}
});
final _validateFirstName = StreamTransformer<String, String>.fromHandlers(
handleData: (firstName, sink) {
if (Validator.isValidFirstName(firstName)) {
sink.add(firstName);
} else {
sink.addError(StringConstant.firstNameValidateMessage);
}
});
bool validateFields() {
if (Validator.isNotNullEmptyFalseOrZero(_email.value) &&
Validator.isNotNullEmptyFalseOrZero(_password.value) &&
Validator.isValidFirstName(_firstName.value) &&
// _phoneNumber.value != null &&
//// _phoneNumber.value.is &&
// _profilePicture.value != null &&
// _profilePicture.value.isNotEmpty &&
Validator.isValidEmail(_email.value) &&
_password.value.length >= 6) {
return true;
} else {
return false;
}
}
void dispose() async {
await _email.drain();
_email.close();
await _password.drain();
_password.close();
await _firstName.drain();
_firstName.close();
await _phoneNumber.drain();
_phoneNumber.close();
await _profilePicture.drain();
_profilePicture.close();
await _profilePictureURL.drain();
_profilePictureURL.close();
await _createAccountStatus.drain();
_createAccountStatus.close();
}
}
I've tried to kinda copy other parts and make them work but every time on the journey I seem to come across an error.
Any ideas on how to pick an image from the gallery and update the _bloc.profilePicture ?
Thanks!
I'm not really sure what you have and haven't tried yet.
But I was able to get your code working like this
create_account_bloc.dart
final _profilePicture = BehaviorSubject<File>();
Observable<File> get profilePicture => _profilePicture.stream;
Function(File) get changeProfilePicture => _profilePicture.sink.add;
void getImage() {
_repository.getImage().then((value) {
_profilePicture.sink.add(value);
});
}
void takeImage() {
_repository.takeImage().then((value) {
_profilePicture.sink.add(value);
});
}
Related
I'm facing some issues quite the time lately. I'm using the scoped_model package for my application. But i'm getting the problem that when i'm searching for stuff or i turn my screen to landscape modus it builds twice.
When this happens i'm getting duplicate json because it is rebuilding twice.
I'm getting another issue when i have searched and i go back my custom scroll view is not working anymore. I can't scroll down anymore.
I have writtin the following files:
main.dart:
class GetRelations extends StatefulWidget {
#override
RelationsPage createState() => RelationsPage();
}
class RelationsPage extends State<GetRelations> {
final RelationScopedModel relationScopedModel = RelationScopedModel();
#override
Widget build(BuildContext context) {
relationScopedModel.initializeValues();
return Scaffold(
body: ScopedModel<RelationScopedModel>(
model: relationScopedModel,
child: Container(
child: SearchScreen(),
),
),
);
}
}
search_screen.dart:
class SearchScreen extends StatefulWidget {
#override
SearchScreenState createState() {
return new SearchScreenState();
}
}
class SearchScreenState extends State<SearchScreen> {
final RelationScopedModel relationScopedModel = RelationScopedModel();
ScrollController controller;
int page = 0;
bool atEnd = false;
#override
void initState() {
super.initState();
controller = new ScrollController()..addListener(_scrollListener);
}
#override
void dispose() {
super.dispose();
controller.dispose();
}
void _scrollListener() {
var props = RelationScopedModel.of(context);
/// When reload we check if skip == 0, if skip is 0 then page has to become 0 too.
if (props.skip == 0) {
page = 0;
}
/// Checking if user is at the end of the screen, then let's receive some new content.
if (controller.position.pixels == controller.position.maxScrollExtent) {
/// If it is at the end, we have to set atEnd to true.
if (props.atTheEnd == true) {
atEnd = props.atTheEnd;
return;
}
/// If it has no more pages, return.
if (props.hasMorePages == false) {
return;
}
/// If it is has more stuff, load it in!
if (!props.isLoadingMore && props.hasMorePages) {
page++;
props.getRelations(props.search, page);
}
}
}
#override
Widget build(BuildContext context) {
/// Go back to last page by using WillPopScope.
return WillPopScope(
onWillPop: () {
Navigator.pop(context);
},
child: new Scaffold(
drawer: new DrawerOnly(),
/// We are using Scoped Model to load the json in the sliver list.
body: ScopedModelDescendant<RelationScopedModel>(
builder: (context, child, model) {
return CustomScrollView(
controller: controller,
slivers: <Widget>[
SliverAppBar(
title: SearchWidget(
performSearch: model.getRelations,
),
floating: true,
pinned: true,
),
model.isLoading && model.atTheEnd
? SliverFillRemaining(
child: Center(
child: CircularProgressIndicator(),
),
)
: model.getRelationCount() < 1
? SliverFillRemaining(
child: Center(
child: Text(
model.statusText,
style: Theme.of(context).textTheme.headline,
),
),
)
: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == model.getRelationCount() + 1) {
if (model.hasMorePages == true) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 16.0),
child: Center(
child: CircularProgressIndicator()),
);
}
return Container(width: 0, height: 0);
} else if (index == 0) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey[300]))),
child: Text(
"Relaties",
style: Theme.of(context)
.textTheme
.body2
.copyWith(color: Colors.white),
),
);
} else {
return Container(
child: Column(
children: <Widget>[
InkWell(
child: RelationItem(
model.relation[index - 1]),
onTap: () {
var id =
model.relation[index - 1].id;
Navigator.of(context).push(
new MaterialPageRoute(
builder: (BuildContext
context) =>
new DetailScreen(id)));
},
),
],
),
);
}
},
childCount: model.getRelationCount() + 2,
),
)
],
);
},
),
),
);
}
}
search.dart (this is my searchwidget):
class SearchWidget extends StatelessWidget { final performSearch;
const SearchWidget({Key key, #required this.performSearch}) : super(key: key);
#override Widget build(BuildContext context) {
print('wat gebeurt er');
return Card(
elevation: 3.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(2.0)),
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 15.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
GestureDetector(
child: Icon(
Icons.search,
color: Colors.white,
),
onTap: () {},
),
SizedBox(
width: 10.0,
),
Expanded(
child: TextField(
decoration: InputDecoration(
hintStyle: TextStyle(fontSize: 14.0, color: Colors.white),
border: InputBorder.none,
hintText: "Zoeken..."),
onChanged: (String search) {
if (search.length > 2) {
performSearch(search);
}
if (search.length == 0) {
performSearch(search);
}
},
),
),
InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => SettingsScreen()));
},
child: Icon(
FontAwesomeIcons.slidersH,
color: Colors.white,
),
),
],
),
),
); } }
relation_scoped_model.dart:
class RelationScopedModel extends Model {
List<GetRelation> _relation = [];
List<GetContacts> _contacts = [];
List<GetLocations> _locations = [];
bool _isLoading = false;
String _statusText = "Start met zoeken...";
bool _hasMorePages = true;
int _skip = 0;
int skipMore = 0;
String _search;
bool _isLoadingMore = false;
bool atTheEnd = false;
List<Map<String, String>> _listingTypeList = [
{"name": "Buy", "value": "buy"},
{"name": "Rent", "value": "rent"},
];
String _listingType;
List<Map<String, String>> _sortList = [
{"name": "Relevancy", "value": "relevancy"},
{"name": "Bedroom (Ascending)", "value": "bedroom_lowhigh"},
{"name": "Bedroom (Descending)", "value": "bedroom_highlow"},
{"name": "Price (Ascending)", "value": "price_lowhigh"},
{"name": "Price (Descending)", "value": "price_highlow"},
{"name": "Newest", "value": "newest"},
{"name": "Oldest", "value": "oldest"},
{"name": "Random", "value": "random"},
{"name": "Distance", "value": "distance"}
];
String _sort;
List<GetRelation> get relation => _relation;
List<GetContacts> get contacts => _contacts;
List<GetLocations> get locations => _locations;
bool get isLoading => _isLoading;
String get statusText => _statusText;
bool get hasMorePages => _hasMorePages;
String get search => _search;
int get skip => _skip;
bool get isLoadingMore => _isLoadingMore;
List<Map<String, String>> get listingTypeList => _listingTypeList;
String get listingType => _listingType;
List<Map<String, String>> get sortList => _sortList;
String get sort => _sort;
int getRelationCount() => _relation.length;
void initializeValues() async {
_relation.clear();
SharedPreferences prefs = await SharedPreferences.getInstance();
_listingType = prefs.getString('listingType') ?? 'rent';
_sort = prefs.getString('sort') ?? 'relevancy';
getRelations(search);
}
var _skiptotal = 10;
var lastSearch;
Future<dynamic> _getData(String search, [int page = 0]) async {
/// When page is 0 we don't have to skip content, if page is 1 or higher it will have to skip content
if (page != 0) {
var skipPage = page * _skiptotal;
_skip = skipPage;
}
/// If page == 0, we have to set page and _skip to 0.
else {
_skip = 0;
page = 0;
}
/// When nothing is filled in the input search, we have to set search to single quotes because otherwise it will search on null.
if (search == null) {
search = '';
}
if (lastSearch != search) {
_relation.clear();
lastSearch = '';
lastSearch = search;
page + 1;
}
String _credentials;
SharedPreferences pref = await SharedPreferences.getInstance();
_credentials = (pref.getString("credentials") ?? "Empty");
var res = await http.get(
Uri.encodeFull("$cf_api_RelationsUrl" +
"?$cf_api_SkipParameter=$_skip&$cf_api_SearchParameter=$search&$cf_api_LimitParameter=10"),
headers: {
"content-type": "application/json",
"accept": "application/json",
'cookie': '$_credentials'
});
var decodedJson = json.decode(res.body, reviver: (k, v) {
if (k == "status" && v == false) {
_hasMorePages = false;
return v;
} else {
return v;
}
});
if (_hasMorePages == false) {
return decodedJson;
} else {
List list = List();
list = json.decode(res.body) as List;
if (list.length < 10) {
atTheEnd = true;
_hasMorePages = false;
return decodedJson;
}
}
return decodedJson;
}
Future getRelations(String search, [int page = 0]) async {
if (page == 0) {
page = 0;
_isLoading = true;
_relation.clear();
} else {
_isLoadingMore = true;
}
_search = search;
var responseData = await _getData(search, page);
notifyListeners();
var result = responseData
.map(
(data) => serializers.deserializeWith(GetRelation.serializer, data))
.toList();
result.forEach((relations) {
_relation.add(relations);
});
if (result.isEmpty) {
_statusText = "Nothing Found";
}
if (page == 0) {
_isLoading = false;
} else {
_isLoadingMore = false;
}
if (atTheEnd == true) {
return true;
}
// notifyListeners();
}
void setListingType(String value) async {
_listingType = value;
getRelations(search);
notifyListeners();
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('listingType', _listingType);
}
void setSort(String value) async {
_sort = value;
getRelations(search);
notifyListeners();
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('sort', _sort);
}
/// Wraps [ScopedModel.of] for this [Model].
static RelationScopedModel of(BuildContext context) =>
ScopedModel.of<RelationScopedModel>(context);
}
I'm guessing that the problem is that my scopedmodeldescendant isn't in the init state. But i'm not sure and i hope somebody knows what the problem is with this code.
Thanks in advance
Greetings,
Jente
UPDATE:
I got my customscrollview is working when i set atTheEnd to false and hasMorePages to true in my relation_scoped_model at initializeValues().
The only problem i'm still facing is that i'm getting duplicates, which happens because my screen is rebuilding twice.
When using scoped model, you should wrap the widgets that should respond to changes one by one with scoped model descendants. This way only those widgets can rebuild rather than the whole page rebuilding.
Hello I am working on a instagram clone using flutter and for the feed I want the images to show up in a horizontal card carousel view for the posts of from the user you follow
here is the current feed code:
import 'package:flutter/material.dart';
import 'image_post.dart';
import 'dart:async';
import 'package:async/async.dart';
import 'main.dart';
import 'dart:io';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class Feed extends StatefulWidget {
_Feed createState() => new _Feed();
}
class _Feed extends State<Feed> {
List<ImagePost> feedData;
#override
void initState() {
super.initState();
this._loadFeed();
}
buildFeed() {
if (feedData != null) {
return new ListView(
children: feedData,
);
} else {
return new Container(
alignment: FractionalOffset.center,
child: new CircularProgressIndicator());
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text('InstaGroove',
style: const TextStyle(
fontFamily: "Billabong", color: Colors.black, fontSize: 35.0)),
centerTitle: true,
backgroundColor: Colors.white,
),
body: new RefreshIndicator(
onRefresh: _refresh,
child: buildFeed(),
),
);
}
Future<Null> _refresh() async {
await _getFeed();
setState(() {
});
return;
}
_loadFeed() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String json = prefs.getString("feed");
if (json != null) {
List<Map<String, dynamic>> data =
jsonDecode(json).cast<Map<String, dynamic>>();
List<ImagePost> listOfPosts = _generateFeed(data);
setState(() {
feedData = listOfPosts;
});
} else {
_getFeed();
}
}
_getFeed() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String userId = googleSignIn.currentUser.id.toString();
var url =
'https://us-central1-mp-rps.cloudfunctions.net/getFeed?uid=' + userId;
var httpClient = new HttpClient();
List<ImagePost> listOfPosts;
String result;
try {
var request = await httpClient.getUrl(Uri.parse(url));
var response = await request.close();
if (response.statusCode == HttpStatus.OK) {
String json = await response.transform(utf8.decoder).join();
prefs.setString("feed", json);
List<Map<String, dynamic>> data =
jsonDecode(json).cast<Map<String, dynamic>>();
listOfPosts = _generateFeed(data);
} else {
result =
'Error getting a feed:\nHttp status ${response.statusCode}';
}
} catch (exception) {
result =
'Failed invoking the getFeed function. Exception: $exception';
}
print(result);
setState(() {
feedData = listOfPosts;
});
}
List<ImagePost> _generateFeed(List<Map<String, dynamic>> feedData) {
List<ImagePost> listOfPosts = [];
for (var postData in feedData) {
listOfPosts.add(new ImagePost.fromJSON(postData));
}
return listOfPosts;
}
}
I've done some research on google and youtube and I can't figure out how to add the card carousel to the feed part of my app. I'm still fairly new to flutter so any help would be amazing! thanks in advance!! :)
Try this code. This is a sample image carousel screen. For now, there is hardcoded image list used, you can create dynamic list of Image.network() based on image list from api call.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class TestImageCarousel extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _TestImageCarousel();
}
}
class _TestImageCarousel extends State<TestImageCarousel> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Material(
child: Stack(
children: <Widget>[
PageView(
onPageChanged: (id) {
//code to handle page change
},
scrollDirection: Axis.horizontal,
children: getImageList(),
),
],
));
}
}
/*
Get image list to populate in carousel
*/
getImageList() {
return <Widget>[
Padding(
padding: EdgeInsets.only(left: 1, right: 1),
child: Image.network("https://www.gstatic.com/webp/gallery/1.jpg",
fit: BoxFit.fill),
),
Padding(
padding: EdgeInsets.only(left: 1, right: 1),
child: Image.network("https://www.gstatic.com/webp/gallery/2.jpg",
fit: BoxFit.fill),
),
Padding(
padding: EdgeInsets.only(left: 1, right: 1),
child: Image.network("https://www.gstatic.com/webp/gallery/3.jpg",
fit: BoxFit.fill),
),
Padding(
padding: EdgeInsets.only(left: 1, right: 1),
child: Image.network("https://www.gstatic.com/webp/gallery/4.jpg",
fit: BoxFit.fill),
)
];
}
Now in this you can make a network call to get feed image list and bind list to Image.network("src").You can also add a page controller to get automatic carousel effects. Add this images in InkWell() to get onTap listeners.
Here I want to make a masking effect, like Wizard School app.
Here I am using RenderProxyBox but I can do only one mask at one time, I want to give multi-time effect. Using blendMode.clear here I am removing the cover image, and revealing reveals the image. So, is there any other way to implement multi masking effect as given in Expected section.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart: math' as math;
class DemoApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Scratch Card',
home: Scaffold(
appBar: AppBar(
title: Text('Scratch Card'),
),
body: Material(
child: Center(
child: SizedBox(
width: 500.0,
height: 500.0,
child: Stack(
children: <Widget>[
ScratchCard(
cover: Stack(
fit: StackFit.expand,
children: <Widget>[
FittedBox(
child: Image.asset(
'assets/bird.jpg',
repeat: ImageRepeat.repeat,
),
),
],
),
reveal: DecoratedBox(
decoration: const BoxDecoration(color: Colors.black),
child: Center(
child:
FittedBox(child: Image.asset('assets/flower.jpg')),
),
),
strokeWidth: 15.0,
finishPercent: 50,
onComplete: () => print('The card is now clear!'),
),
],
),
),
),
),
),
);
}
}
class ScratchCard extends StatefulWidget {
const ScratchCard({
Key key,
this.cover,
this.reveal,
this.strokeWidth = 25.0,
this.finishPercent,
this.onComplete,
}) : super(key: key);
final Widget cover;
final Widget reveal;
final double strokeWidth;
final int finishPercent;
final VoidCallback onComplete;
#override
_ScratchCardState createState() => _ScratchCardState();
}
class _ScratchCardState extends State<ScratchCard> {
_ScratchData _data = _ScratchData();
Offset _lastPoint = null;
Offset _globalToLocal(Offset global) {
return (context.findRenderObject() as RenderBox).globalToLocal(global);
}
double _distanceBetween(Offset point1, Offset point2) {
return math.sqrt(math.pow(point2.dx - point1.dx, 2) +
math.pow(point2.dy - point1.dy, 2));
}
double _angleBetween(Offset point1, Offset point2) {
return math.atan2(point2.dx - point1.dx, point2.dy - point1.dy);
}
void _onPanDown(DragDownDetails details) {
_lastPoint = _globalToLocal(details.globalPosition);
}
void _onPanUpdate(DragUpdateDetails details) {
final currentPoint = _globalToLocal(details.globalPosition);
final distance = _distanceBetween(_lastPoint, currentPoint);
final angle = _angleBetween(_lastPoint, currentPoint);
for (double i = 0.0; i < distance; i++) {
_data.addPoint(Offset(
_lastPoint.dx + (math.sin(angle) * i),
_lastPoint.dy + (math.cos(angle) * i),
));
}
_lastPoint = currentPoint;
}
void _onPanEnd(TapUpDetails details) {
final areaRect = context.size.width * context.size.height;
double touchArea = math.pi * widget.strokeWidth * widget.strokeWidth;
double areaRevealed =
_data._points.fold(0.0, (double prev, Offset point) => touchArea);
print('areaRect $areaRect $areaRevealed');
}
#override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onPanDown: _onPanDown,
onPanUpdate: _onPanUpdate,
onTapUp: _onPanEnd,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
widget.reveal,
_ScratchCardLayout(
strokeWidth: widget.strokeWidth,
data: _data,
child: widget.cover,
),
],
),
);
}
}
class _ScratchCardLayout extends SingleChildRenderObjectWidget {
_ScratchCardLayout({
Key key,
this.strokeWidth = 25.0,
#required this.data,
#required this.child,
}) : super(
key: key,
child: child,
);
final Widget child;
final double strokeWidth;
final _ScratchData data;
#override
RenderObject createRenderObject(BuildContext context) {
return _ScratchCardRender(
strokeWidth: strokeWidth,
data: data,
);
}
#override
void updateRenderObject(
BuildContext context, _ScratchCardRender renderObject) {
renderObject
..strokeWidth = strokeWidth
..data = data;
}
}
class _ScratchCardRender extends RenderProxyBox {
_ScratchCardRender({
RenderBox child,
double strokeWidth,
_ScratchData data,
}) : assert(data != null),
_strokeWidth = strokeWidth,
_data = data,
super(child);
double _strokeWidth;
_ScratchData _data;
set strokeWidth(double strokeWidth) {
assert(strokeWidth != null);
if (_strokeWidth == strokeWidth) {
return;
}
_strokeWidth = strokeWidth;
markNeedsPaint();
}
set data(_ScratchData data) {
assert(data != null);
if (_data == data) {
return;
}
if (attached) {
_data.removeListener(markNeedsPaint);
data.addListener(markNeedsPaint);
}
_data = data;
markNeedsPaint();
}
#override
void attach(PipelineOwner owner) {
super.attach(owner);
_data.addListener(markNeedsPaint);
}
#override
void detach() {
_data.removeListener(markNeedsPaint);
super.detach();
}
#override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
context.canvas.saveLayer(offset & size, Paint());
context.paintChild(child, offset);
Paint clear = Paint()..blendMode = BlendMode.clear;
_data._points.forEach((point) =>
context.canvas.drawCircle(offset + point, _strokeWidth, clear));
context.canvas.restore();
}
}
#override
bool get alwaysNeedsCompositing => child != null;
}
class _ScratchData extends ChangeNotifier {
List<Offset> _points = [];
void addPoint(Offset offset) {
_points.add(offset);
notifyListeners();
}
}
output:-
Expected: -
If I change the color/image than-
I want to change the color/image with the new one and keep previous one.
Step 1 convert JPG/PNG to ui.Image format using this code.
ui.Image uiImage;
static Future<void> cacheImage(String asset) async {
if (maskImageMap[asset] == null) {
try {
ByteData data = await rootBundle.load(asset);
ui.Codec codec = await ui.instantiateImageCodec(
data.buffer.asUint8List(),
);
ui.FrameInfo fi = await codec.getNextFrame();
uiImage = fi.image;
} catch (e) {
print(e);
}
}
}
Step 2: Divide the image in the pixel format.
final Float64List deviceTransform = new Float64List(16)
..[0] = devicePixelRatio
..[5] = devicePixelRatio
..[10] = 1.0
..[15] = 3.5;
Step 3: Use ImageShader to give mask filter effect.
canvas.saveLayer();
Paint().shader = ImageShader(uiImage,
TileMode.repeated, TileMode.repeated, deviceTransform);
If this answer is not enough then here is my git link
A Paint Application
I'm trying to take video by using camera plugin and when I run project in Xcode to run real device since VS code doesn't recognize my phone.
The problem is when I switch camera front to rear like more than 5 less than 10 times even I call dispose(), app crash. Also when recording video and play recorded video, almost 50 ~ 60% crash.
Xcode says
Message from debugger: Terminated due to memory issue
I saw that flutter doesn't have any memory leaks and we don't need to care about memory management.
So, why is this happing?
Am I missing something or fluter error?
Does anyone know what's going on?
Code:
class CameraScreenState extends State<CameraScreen>
with TickerProviderStateMixin {
List<CameraDescription> _cameraDescriptions;
CameraController _cameraController;
VideoPlayerController _videoController;
AnimationController _animationController;
String imagePath;
String videoPath;
VoidCallback videoPlayerListener;
bool _isSelfie = false;
bool _isRecorded = false;
bool _isRecording = false;
bool _isReady = false;
#override
void initState() {
super.initState();
_setupCameras();
}
Future<void> _setupCameras() async {
try {
_cameraDescriptions = await availableCameras();
_cameraController = new CameraController(_cameraDescriptions[0], ResolutionPreset.high);
await _cameraController.initialize();
} on CameraException catch (_) {
// do something on error.
}
if (!mounted) return;
setState(() {
_isReady = true;
});
_animationController = new AnimationController(
vsync: this,
duration: new Duration(seconds: 15),
);
}
#override
void deactivate() {
_videoController?.setVolume(0.0);
_videoController?.removeListener(videoPlayerListener);
super.deactivate();
}
#override
void dispose() {
_cameraController?.dispose();
_animationController?.dispose();
_videoController?.dispose();
super.dispose();
}
String timestamp() => new DateTime.now().millisecondsSinceEpoch.toString();
Future _changeDirection(CameraDescription cameraDescription) async {
await _cameraController.dispose().then((_) async {
_cameraController =
new CameraController(cameraDescription, ResolutionPreset.high);
_cameraController.addListener(() {
if (mounted) setState(() {});
if (_cameraController.value.hasError) {
print(_cameraController.value.errorDescription);
}
});
try {
await _cameraController.initialize();
} on CameraException catch (e) {
print(e);
}
if (mounted) {
setState(() {});
}
});
}
_toSelfie() {
_changeDirection(_cameraDescriptions[1]);
}
_toForward() {
_changeDirection(_cameraDescriptions[0]);
}
void onVideoRecordButtonPressed() {
startVideoRecording().then((String filePath) {
if (mounted) setState(() {});
if (filePath != null) print('Saving video to $filePath');
});
}
void onStopButtonPressed() {
stopVideoRecording().then((_) {
if (mounted) setState(() {});
print('Video recorded to: $videoPath');
});
}
Future<String> startVideoRecording() async {
if (!_cameraController.value.isInitialized) {
print('Error: select a camera first.');
return null;
}
final Directory extDir = await getTemporaryDirectory();
final String dirPath = '${extDir.path}/Movies/flutter_test';
await new Directory(dirPath).create(recursive: true);
final String filePath = '$dirPath/${timestamp()}.mp4';
if (_cameraController.value.isRecordingVideo) {
// A recording is already started, do nothing.
return null;
}
try {
videoPath = filePath;
await _cameraController.startVideoRecording(filePath).then((_) async {
final animation = _animationController.forward(from: 0.0);
await animation.then((_) {
setState(() {
onStopButtonPressed();
_isRecorded = true;
});
});
});
} on CameraException catch (e) {
print(e);
return null;
}
return filePath;
}
Future<void> stopVideoRecording() async {
if (!_cameraController.value.isRecordingVideo) {
return null;
}
try {
await _cameraController.stopVideoRecording();
} on CameraException catch (e) {
print(e);
return null;
}
await _startVideoPlayer();
}
Future<void> _startVideoPlayer() async {
final VideoPlayerController vcontroller =
new VideoPlayerController.file(new File(videoPath));
videoPlayerListener = () {
if (_videoController != null && _videoController.value.size != null) {
// Refreshing the state to update video player with the correct ratio.
if (mounted) setState(() {});
_videoController.removeListener(videoPlayerListener);
}
};
vcontroller.addListener(videoPlayerListener);
await vcontroller.setLooping(true);
await vcontroller.initialize();
await _videoController?.dispose();
if (mounted) {
setState(() {
imagePath = null;
_videoController = vcontroller;
});
}
await vcontroller.play();
}
_startVideoRecordingCoountDown() {
onVideoRecordButtonPressed();
}
_stopVideoRecordingCountDown() {
setState(() {
_animationController.stop();
onStopButtonPressed();
_isRecorded = true;
});
}
#override
Widget build(BuildContext context) {
if (!_isReady) {
return new Container(
color: Colors.black,
child: new Center(child: new CircularProgressIndicator()));
}
return new Scaffold(
backgroundColor: Colors.black,
body: !_isRecorded
? new Stack(
children: <Widget>[
new AspectRatio(
aspectRatio: _cameraController.value.aspectRatio,
child: new CameraPreview(_cameraController),
),
!_isRecording
? new Align(
alignment: Alignment.topLeft,
child: new Container(
child: new FloatingActionButton(
heroTag: 'start',
backgroundColor: Colors.black.withOpacity(0.001),
elevation: 50.0,
child: new Center(
child: new Icon(Icons.clear, size: 28.0)),
onPressed: () {
Navigator.of(context).pop();
},
),
),
)
: new Container(),
new Align(
alignment: new Alignment(0.0, 1.0),
child: new Container(
margin: const EdgeInsets.only(bottom: 10.0),
child: new FloatingActionButton(
elevation: 30.0,
backgroundColor: Colors.white,
foregroundColor: Colors.amber,
child: _animationController.isAnimating
? new Countdown(
animation: new StepTween(
begin: 16,
end: 0,
).animate(_animationController),
)
: new Icon(Icons.play_arrow),
onPressed: () {
setState(() {
_isRecording = true;
});
!_animationController.isAnimating
? _startVideoRecordingCoountDown()
: _stopVideoRecordingCountDown();
},
),
),
),
!_isRecording
? new Align(
alignment: Alignment.bottomRight,
child: new Container(
margin:
const EdgeInsets.only(right: 15.0, bottom: 10.0),
child: new FloatingActionButton(
elevation: 50.0,
backgroundColor: Colors.black.withOpacity(0.001),
child: new Icon(Icons.cached, size: 35.0),
onPressed: () {
setState(() {
_isSelfie ? _toForward() : _toSelfie();
_isSelfie
? _isSelfie = false
: _isSelfie = true;
});
},
),
),
)
: new Container(),
],
fit: StackFit.expand,
)
: _videoController == null
? new Container(
color: Colors.black,
child: new Center(child: new CircularProgressIndicator()))
: _videoController.value.size != null
? new AspectRatio(
aspectRatio: _videoController.value.aspectRatio,
child: new VideoPlayer(_videoController),
)
: new Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: new VideoPlayer(_videoController)),
);
}
}
I am developing movie discovering app on flutter and I need to save the list of now playing movies on local storage for offline use and so how can I do that
Future> getNowPlayingMovies() async {
final String nowPlaying =
'https://api.themoviedb.org/3/tv/airing_today?api_key=' +
'$myapikey' +
'&page=' +
'1';
var httpClient = new HttpClient();
try {
// Make the call
var request = await httpClient.getUrl(Uri.parse(nowPlaying));
var response = await request.close();
if (response.statusCode == HttpStatus.OK) {
var jsonResponse = await response.transform(utf8.decoder).join();
// Decode the json response
var data = jsonDecode(jsonResponse);
// Get the result list
List results = data["results"];
print(results);
// Get the Movie list
List<moviemodel> movieList = createNowPlayingMovieList(results);
// Print the results.
return movieList;
} else {
print("Failed http call.");
}
} catch (exception) {
print(exception.toString());
}
return null;}
List<moviemodel> createNowPlayingMovieList(List data) {
List<Searchmodel> list = new List();
for (int i = 0; i < data.length; i++) {
var id = data[i]["id"];
String title = data[i]["name"];
String posterPath = data[i]["poster_path"];
String mediatype = data[i]["media_type"];
moviemodel movie = new moviemodel(id, title, posterPath, mediatype);
list.add(movie);
}
return list; }
List<Widget> createNowPlayingMovieCardItem(
List<moviemodel> movies, BuildContext context) {
// Children list for the list.
List<Widget> listElementWidgetList = new List<Widget>();
if (movies != null) {
var lengthOfList = movies.length;
for (int i = 0; i < lengthOfList; i++) {
Searchmodel movie = movies[i];
// Image URL
var imageURL = "https://image.tmdb.org/t/p/w500/" + movie.posterPath;
// List item created with an image of the poster
var listItem = new Padding(
padding: const EdgeInsets.all(8.0),
child: new Container(
width: 105.0,
height: 155.0,
child: new Column(
children: <Widget>[
new GestureDetector(
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (_) => new Detail(movie.id)),
);
},
child: new Container(
width: 105.0,
height: 155.0,
child: new ClipRRect(
borderRadius: new BorderRadius.circular(7.0),
child: new Hero(
tag: movie.title,
child: new FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image: imageURL,
fit: BoxFit.cover,
),
),
),
decoration: new BoxDecoration(boxShadow: [
new BoxShadow(
color: Colors.black12,
blurRadius: 10.0,
offset: new Offset(0.0, 10.0)),
]),
),
),
new Padding(
padding: const EdgeInsets.only(top: 18.0),
child: new Text(
movie.title,
maxLines: 2,
),
)
],
),
),
);
;
listElementWidgetList.add(listItem);
}
} else {
print("no movie search");
}
return listElementWidgetList;}
thank you!
Use path_provider :
Find the correct local path:
Future get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Create a reference to the file location
Future get _localFile async {
final path = await _localPath;
return File('$path/yourfile.txt');
}
Write data to the file:
Future writeCounter(int counter) async {
final file = await _localFile;
// Write the file
return file.writeAsString('blah bla blah');
}
Read data from the file:
Future readCounter() async {
try {
final file = await _localFile;
// Read the file
String contents = await file.readAsString();
return int.parse(contents);
} catch (e) {
// If we encounter an error, return 0
return 0;
}
}
If you print contents = "blah blah blah"
Documentation: https://flutter.io/cookbook/persistence/reading-writing-files/
And File has a lot of methods tha can help you, checkout:
https://docs.flutter.io/flutter/dart-io/File-class.html