I want to load an image to cache. So I used CachedNetworkImage for that. When a user logged in through gmail account I get the image url and show the image. But I need to keep it in cache.
Here is my code:
new Center(
child: new Column(
children: <Widget>[
new CircleAvatar(
new CachedNetworkImage(
placeholder: CircularProgressIndicator(),
imageUrl: widget.currentUser?.profilUrl,
),
),
],
),
)
I used CachedNetworkImageProvider also but same error is coming for both. The error is
type'CachedNetworkImage'is not a subtype of type 'ImageProvider<dynamic>'
The widget CircleAvatar receives an ImageProvider.
The cached_network_image package offers you two classes to use:
CachedNetworkImage a Widget you can use to display a cached network image.
CachedNetworkImageProvider an ImageProvider providing the cached image.
Therefore you gotta use CachedNetworkImageProvider(2.), if you want to pass it to the CircleAvatar.
Here is a complete example, that you can copy & paste for trying out:
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(),
home: Scaffold(
body: Center(
child: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(
'https://upload.wikimedia.org/wikipedia/commons/thumb/7/71/Bill_Gates_Buys_Skype_%285707954468%29.jpg/2560px-Bill_Gates_Buys_Skype_%285707954468%29.jpg'
),
),
),
)
);
}
}
CircleAvatar backgroundImage property need ImageProvider , Not a Image widget.
ImageProviders :- NetworkImage(), AssetImage() & CachedNetworkImageProvider().
Image Widgets :- Image() , CircleAvatar() , CachedNetworkImage()
So CircleAvatar() & CachedNetworkImage() is also a image displaying widget same as Image() widget, that we used in generally.
Every image displaying widgets need image providers.
So in this case you have to use CircleAvatar() widget with CachedNetworkImageProvider(), because this CachedNetworkImageProvider() provide image data that needs by CircleAvatar() widget.
Just like #Niklas say.
The widget CircleAvatar receives an ImageProvider.
this is how to use:
new Center(
child: new Column(
children: <Widget>[
new CircleAvatar(
backgroundImage: new CachedNetworkImageProvider(
widget.currentUser?.profilUrl,
)
),
],
),
)
I don't know whether it may help someone but he mention how to handle network image error. I usually used Image.Network(Flutter build widget) to handle such errors. this is an example
Image.network(
products[index].image!,
errorBuilder: (BuildContext context,Object exception,StackTrace
stackTrace) {
return Image.asset('img/no_image.png');
},)
the errorbuilder help to show another image in case network image has not been successfully found.
Related
Here is what I am trying to do
child: FutureBuilder(
future: ProductRepo().getMyProduct(),
builder: (BuildContext context, AsyncSnapshot res){
if(res.data==null){
return Container(
child: Text('this is nice'),
);
}
return Container(
child: Column(
children: <Widget>[
Card(
child: Text('I just want to loop over this card :)'),
),
],
)
);
}
),
I always find people looping over listView.builder. Anyone can help, please. Thank you.
I think you are making a bit of confusion about the use of these widgets.
Indeed both ListView and Column can be used to display a list of widgets, BUT, ListView.builder(...) provides a way to reuse the widgets thus is more memory efficient when you have to create a large number of widgets.
For example, when you want to display a list of electronics for an e-commerce app. For each electronic item you have a photo, title & price, in this case you would want to use a ListView.builder, because the list can be huge and you don't want to run out of memory.
Now on the other hand, the Column should be used when you have a small number of widgets that should be displayed in a list-like way (or one beneath the other).
For your case, if you want to transform the list of objects that you have, into a list of cards you can do something like this:
FutureBuilder(
future: ProductRepo().getMyProduct(),
builder: (BuildContext context, AsyncSnapshot res){
if(res.data==null){
return Container(
child: Text('this is nice'),
);
}
return Container(
child: Column(
children: res.data.map((item) {
return Card(child: Text(item.name));
}).toList());
);
}
),
I've assumed that res.data is a list of elements and each element has a property called name. Also in the return Card(...) line you can do extra processing of the item if you need to do so.
Hope that this can help you :).
If you have to do more processing
You can extract the processing in a method or a chain of methods something like this:
List<Widget>prepareCardWidgets(List<SomeObject> theObjects){
//here you can do any processing you need as long as you return a list of ```Widget```.
List<Widget> widgets = [];
theObjects.forEach((item) {
widgets.add(Card(child: Text(item.name),));
});
return widgets;
}
Then you can use it like this:
FutureBuilder(
future: ProductRepo().getMyProduct(),
builder: (BuildContext context, AsyncSnapshot res){
if(res.data==null){
return Container(
child: Text('this is nice'),
);
}
return Container(
child: Column(
children: prepareCardWidgets(res.data)
);
}
),
I'm trying to create a card with a text within a container but I would like to show only a part of the text and when the user click on "show more", show the rest. I saw a Widget to construct text like this here, but I need expand the card container either and I don't know how to do that because I need to know how many lines the text have to expand with the correctly size. Exists a way to calculate the size according the number of lines or characters?
I tried to create the card as follows, where the DescriptionText is the Widget on the link and specify a minHeight in the Container in the hope of expanding the container along with the text but did not work.
Widget _showAnswerCard(Answer answer, User user) {
return Card(
elevation: 3.0,
color: Theme.of(context).backgroundColor,
child: Container(
constraints: BoxConstraints(minHeight: 90),
padding: EdgeInsets.all(10.0),
child: Flex(
direction: Axis.horizontal,
children: <Widget>[
Expanded(flex: 1, child: _showUserAvatar(answer)),
Expanded(flex: 3, child: _showAnswerDetails(answer, user)),
],
),
));
}
Widget _showAnswerDetails(Answer answer, User user) {
return Flex(
direction: Axis.vertical,
children: <Widget>[
Expanded(
flex: 3,
child: DescriptionTextWidget(text: answer.content),
),
Expanded(
flex: 1,
child: _showAnswerOptions(),
)
],
);
}
I'll really appreciate if someone could help me with that.
Just use Wrap widget to wrap your Card widget.
Based on your link for suggested answer. I did change to use Wrap widget.
Jus do copy/paste below code and check.
import 'package:flutter/material.dart';
class ProductDetailPage extends StatelessWidget {
final String description =
"Flutter is Google’s mobile UI framework for crafting high-quality native interfaces on iOS and Android in record time. Flutter works with existing code, is used by developers and organizations around the world, and is free and open source.";
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text("Demo App"),
),
body: new Container(
child: new DescriptionTextWidget(text: description),
),
);
}
}
class DescriptionTextWidget extends StatefulWidget {
final String text;
DescriptionTextWidget({#required this.text});
#override
_DescriptionTextWidgetState createState() =>
new _DescriptionTextWidgetState();
}
class _DescriptionTextWidgetState extends State<DescriptionTextWidget> {
bool flag = true;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Wrap(
children: <Widget>[
Card(
margin: EdgeInsets.all(8),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
child: Column(
children: <Widget>[
Container(
child: Text(
widget.text,
overflow: flag ? TextOverflow.ellipsis : null,
style: TextStyle(
fontSize: 15,
),
),
),
InkWell(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text(
flag ? "show more" : "show less",
style: new TextStyle(color: Colors.blue),
),
],
),
onTap: () {
setState(() {
flag = !flag;
});
},
),
],
)),
),
],
);
}
}
Result:
The solution I can think of is to use two labels, one for displaying only one line of text and one for displaying all the text. When the button is clicked, the two labels are alternately displayed in an animated manner. There is no computer at the moment, it is not convenient to verify, I hope to give you some help in the implementation of the program.
I question myself sometimes, whether I'm dumb, or Dart (Flutter) is weird.
How does this not work?
I'm using https://github.com/apptreesoftware/flutter_google_map_view
I show a map, and have added markers.
Since the package supports listeners, when a marker is tapped, I want to show a modal.
Does the listener work? Yep, because the print statement happens.
Does the modal work? I don't know. No error shows, nothing!
mapView.onTouchAnnotation.listen((annotation) {
print(annotation);
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 260.0,
child: Text('Text'),
);
},
);
});
Please, what is the magic bullet?
Edit
Lemme thrown in more flesh. This is my Scaffold widget.
MapView mapView = new MapView();
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
key: scaffoldKey,
appBar: new AppBar(
title: new Text('Map View Example'),
),
body: new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
showMap(context),
],
),
),
);
And showMap(...) looks like this:
showMap(context) {
mapView.show(
new MapOptions(
mapViewType: MapViewType.normal,
showUserLocation: true,
showMyLocationButton: true,
showCompassButton: true,
initialCameraPosition:
new CameraPosition(new Location(5.6404963, -0.2285315), 15.0),
hideToolbar: false,
title: "Dashboard"),
// toolbarActions: [new ToolbarAction("Close", 1)],
);
mapView.onMapReady.listen((_) {
mapView.setMarkers(_markers);
});
mapView.onTouchAnnotation.listen((annotation) {
print(annotation);
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 260.0,
child: Text('Text'),
);
},
);
});
}
The reason you've having issues is that your context doesn't contain a scaffold. If you look what you're doing in your code, your context actually comes from the widget enclosing your scaffold.
YourWidget <------------ context
MaterialApp
Scaffold
AppBar
Column
showMap....
There are a couple of ways to get around this. You can use a Builder widget something like this:
body: new WidgetBuilder(
builder: (context) => new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
showMap(context),
],
)
),
in which case the context is actually rooted below the scaffold.
YourWidget
MaterialApp
Scaffold
AppBar
Builder <------------ context
Column
showMap....
However, what I would actually recommend is breaking your class into multiple classes. If your build function gets large enough you have to separate it out into another function (that's only used once), there's a good chance you need a new widget!
You could either make a body widget (probably Stateless), or a widget just for showing the map (Stateless or Stateful depending on your needs)... or more likely both!
Now as to why you're not seeing any errors... are you running in debug or release mode (if you're in debug mode there should be a little banner in the top right of the screen)? If you're in release mode it might ignore the fact that there is no scaffold in the context and fail silently, whereas in debug mode it should throw an assertion error. Running from the IDE or with flutter run generally runs in debug mode, but you may have changed it.
I currently have a MaterialApp in my flutter application which makes the use of the Navigator extremely easy, which is great. But, I'm now trying to figure out how to create more navigators for particular views/widgets. For example I've got a custom tab bar with another widget/view in. I'd now like that widget/view to have it's own navigation stack. So the goal is to keep the tab bar at the top while I navigate to other pages from within my widget/view.
This question is almost exactly this: Permanent view with navigation bar in Flutter but in that code, there is no MaterialApp yet. Nesting MaterialApps give funky results and I don't believe that that would be a solution.
Any ideas?
You can create new Navigator for each page. As a reference check CupertinoTabView
Or simple example:
import 'package:flutter/material.dart';
class Home extends StatelessWidget {
Navigator _getNavigator(BuildContext context) {
return new Navigator(
onGenerateRoute: (RouteSettings settings) {
return new MaterialPageRoute(builder: (context) {
return new Center(
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Text(settings.name),
new FlatButton(
onPressed: () =>
Navigator.pushNamed(context, "${settings.name}/next"),
child: new Text('Next'),
),
new FlatButton(
onPressed: () =>
Navigator.pop(context),
child: new Text('Back'),
),
],
),
);
});
},
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Column(
children: <Widget>[
new Expanded(
child: _getNavigator(context),
),
new Expanded(
child: _getNavigator(context),
),
],
),
);
}
}
void main() {
runApp(new MaterialApp(
home: new Home(),
));
}
you could give first material app's context to secondary material app;
and in secondary material app ,when you need to go back first material app,
you could check whether the secondary app can pop, with this line:
Navigator.of(secondaryContext).canPop()
if true, then you could keep using,else use first material app's context,
like this:
Navigator.of(parentContext).pop();
otherwise
Our app is built on top of Scaffold and to this point we have been able to accommodate most of our routing and navigation requirements using the provided calls within NavigatorState (pushNamed(), pushReplacementNamed(), etc.). What we don't want though, is to have any kind of 'push' animation when a user selects an item from our drawer (nav) menu. We want the destination screen from a nav menu click to effectively become the new initial route of the stack. For the moment we are using pushReplacementNamed() for this to ensure no back arrow in the app bar. But, the slide-in-from-the-right animation implies a stack is building.
What is our best option for changing that initial route without animation, and, can we do that while also concurrently animating the drawer closed? Or are we looking at a situation here where we need to move away from Navigator over to just using a single Scaffold and updating the 'body' directly when the user wants to change screens?
We note there is a replace() call on NavigatorState which we assume might be the right place to start looking, but it's unclear how to access our various routes originally set up in new MaterialApp(). Something like replaceNamed() might be in order ;-)
What you're doing sounds somewhat like a BottomNavigationBar, so you might want to consider one of those instead of a Drawer.
However, having a single Scaffold and updating the body when the user taps a drawer item is a totally reasonable approach. You might consider a FadeTransition to change from one body to another.
Or, if you like using Navigator but don't want the default slide animation, you can customize (or disable) the animation by extending MaterialPageRoute. Here's an example of that:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyCustomRoute<T> extends MaterialPageRoute<T> {
MyCustomRoute({ WidgetBuilder builder, RouteSettings settings })
: super(builder: builder, settings: settings);
#override
Widget buildTransitions(BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
if (settings.isInitialRoute)
return child;
// Fades between routes. (If you don't want any animation,
// just return child.)
return new FadeTransition(opacity: animation, child: child);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Navigation example',
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/': return new MyCustomRoute(
builder: (_) => new MyHomePage(),
settings: settings,
);
case '/somewhere': return new MyCustomRoute(
builder: (_) => new Somewhere(),
settings: settings,
);
}
assert(false);
}
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Navigation example'),
),
drawer: new Drawer(
child: new ListView(
children: <Widget> [
new DrawerHeader(
child: new Container(
child: const Text('This is a header'),
),
),
new ListTile(
leading: const Icon(Icons.navigate_next),
title: const Text('Navigate somewhere'),
onTap: () {
Navigator.pushNamed(context, '/somewhere');
},
),
],
),
),
body: new Center(
child: new Text(
'This is a home page.',
),
),
);
}
}
class Somewhere extends StatelessWidget {
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Text(
'Congrats, you did it.',
),
),
appBar: new AppBar(
title: new Text('Somewhere'),
),
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new DrawerHeader(
child: new Container(
child: const Text('This is a header'),
),
),
],
),
),
);
}
}
Use PageRouteBuilder like:
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => Screen2(),
transitionDuration: Duration.zero,
),
);
And if you want transition, simply add following property to above PageRouteBuilder, and change seconds to say 1.
transitionsBuilder: (_, a, __, c) => FadeTransition(opacity: a, child: c),