I'd like to eliminate the boilerplate code used to wait and then replace an ImageProvider placeholder with a final image.
The following works for an Image Widget.
class FutureImage extends StatelessWidget {
final Future<Image> futureImage;
final Image placeholder;
const FutureImage({Key key, this.futureImage, this.placeholder})
: super(key: key);
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: futureImage,
builder: (BuildContext context, AsyncSnapshot<Image> image) {
if (image.hasData) {
return image.data;
} else {
return placeholder;
}
},
);
}
}
However I need an ImageProvider (because it can be used in places where an Image cannot)
The following doesn't work because ImageProvider isn't a Widget:
class FutureImageProvider extends StatelessWidget {
final Future<ImageProvider> futureImageProvider;
final ImageProvider placeholder;
const FutureImageProvider(
{Key key, this.futureImageProvider, this.placeholder})
: super(key: key);
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: futureImageProvider,
builder: (BuildContext context, AsyncSnapshot<ImageProvider> image) {
if (image.hasData) {
return image.data;
} else {
return placeholder;
}
},
);
}
}
Related
my question is can we use FutureBuilder with Void callbacks on using such methods whome type is
Future<void> fun_name() async{ body }
after creating such methods how can we pass that method to FutureBuilder widget on whatever thy type is/.
Yes
you did't mention what you are trying to accomplish and why you wnna do it but here is demonstration.
Here is demo widget
class Test extends StatelessWidget {
const Test({Key? key}) : super(key: key);
Future<void> fun(){
var s= Future.delayed(Duration(seconds: 10));
return s;
}
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder<void>(
future: fun(),
builder: (snap,context){
return snap.widget; //return widget because builder's return type is widget
},
)
);
}
}
here i am doing it with async and await
class Test2 extends StatelessWidget {
const Test2({Key? key}) : super(key: key);
Future<void> delayedString() async {
await Future.delayed(const Duration(seconds: 2));
return;
}
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder<void>(
future: delayedString(),
builder: (snap,context){
return snap.widget; //return widget because builder's return type is widget
},
)
);
}
}
I'm trying to build a streambuilder.
My problem is that the streambuilder does not update other statefulWidgets inside the streambuilder widget.
Here my code:
class ChatPage extends StatefulWidget {
//pass parm
var chat_object;
ChatPage(this.chat_object, {Key? key}) : super(key: key);
#override
_ChatPage createState() => _ChatPage();
}
class _ChatPage extends State<ChatPage> {
//pass parm
var chat_object;
_ChatPage(this.chat_object);
#override
Widget build(BuildContext context) {
// Full screen width and height
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
// Height (without SafeArea)
var padding = MediaQuery.of(context).padding;
double height1 = height - padding.top - padding.bottom;
// Height (without status bar)
double height2 = height - padding.top;
// Height (without status and toolbar)
double height3 = height - padding.top - kToolbarHeight;
//scaffold
return Scaffold(
appBar: customAppBarGoBack('Chat', context),
body: LimitedBox(
maxHeight: height3,
child: Stack(
children: [
StreamBuilder<dynamic>(
stream: FirebaseFirestore.instance
.collection('chat')
.doc('chat_messages')
.collection(chat_object.chat_message_id[0].trim())
.orderBy('message_time')
.snapshots(),
builder: (
BuildContext context,
AsyncSnapshot<dynamic> snapshot,
) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else {
if (snapshot.hasError) {
return const Text('Error');
} else if (snapshot.hasData) {
//chat room object list
var chat_room_object =
load_object(snapshot.data, chat_object).first;
//chat message object list
var message_object =
load_object(snapshot.data, chat_object).last;
print('message_object: ' + message_object.toString());
return Test(chat_room_object, message_object);
} else {
return const Text('Empty data');
}
}
},
),
],
),
),
drawer: CustomSideBar(context),
);
}
}
//test
class Test extends StatefulWidget {
//pass parm
var chat_room_object;
var message_object;
Test(this.chat_room_object, this.message_object, {Key? key})
: super(key: key);
#override
_Test createState() => _Test(chat_room_object, message_object);
}
class _Test extends State<Test> {
//pass parm
_Test(this.chat_room_object, this.message_object);
var chat_room_object;
var message_object;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: message_object.length,
itemBuilder: (context, i) {
return Text(message_object[i].message.toString());
},
);
}
}
If I build a list inside the streambuilder the list will be update.
But if I build the list inside a new widget and pass the values to list the widget will be not update.
Anyone can give me a simple example how I can update the values in a statefulwidget which is contain in a streambuilder?
Instead of passing parameters in the constructor of state of Test widget, can you try to access them directly from the widget.Example as below.
//test
class Test extends StatefulWidget {
//pass parm
var chat_room_object;
var message_object;
Test(this.chat_room_object, this.message_object, {Key? key})
: super(key: key);
#override
_Test createState() => _Test();
}
class _Test extends State<Test> {
//pass parm
_Test();
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: widget.message_object.length,
itemBuilder: (context, i) {
return Text(widget.message_object[i].message.toString());
},
);
}
}
I have a characterList class that has a final List <Character> character field;
How can I access the character from the SWMain class?
SWMain class:
class _SWMainState extends State<SWMain> {
Icon customIcon = Icon(Icons.search);
static Text titleText = Text("Star Wars API");
Widget customSearchBar = titleText;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar()
body: FutureBuilder<List<Character>>(
future: fetchCharacters(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? CharacterList(character: snapshot.data)
: Center(child: CircularProgressIndicator());
},
),
),
);
}
}
characterList class:
class CharacterList extends StatelessWidget {
final List<Character> character;
CharacterList({Key key, this.character}) : super(key: key);
...
}
CharacterList({Key key, this.character}) : super(key: key);
this is the constructor.
You are using a stateless widget so you can directly access via variable name which is character in your case.
if you are using a stateful widget so you have to access it via widget.character
Thanks.
i've created a StatelessWidget named FirestoreStreamBuilder that create a Stream and return a child.
class FirestoreStreamBuilder extends StatelessWidget {
FirestoreStreamBuilder({#required this.collectionReference, #required this.child, #required this.noDataChild});
final String collectionReference;
final Widget child;
final Widget noDataChild;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection(collectionReference).snapshots() ,
builder: (context, snapshot) {
if (!snapshot.hasData) return noDataChild;
return child;
},
);
}
}
I need to make snapshot accessible from child. How can i do that?
Example:
FirestoreStreamBuilder(
collectionReference: 'collection',
child: Text('I want to acces to snapshot here $snapshot'), //How can I access snapshot from here?
noDataChild: Container(),
)
Create custom callback
typedef OnDataReceived = void Function(DocumentSnapShot);//or your return type
class FirestoreStreamBuilder extends StatelessWidget {
FirestoreStreamBuilder({#required this.collectionReference, #required this.child,
#required this.noDataChild, #required this.onDataReceived});
final String collectionReference;
final Widget child;
final Widget noDataChild;
final OnDataReceived onDataReceived;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection(collectionReference).snapshots() ,
builder: (context, snapshot) {
if (!snapshot.hasData) {
onDataReceived(snapshot);
return noDataChild;
}
return child;
},
);
}
}
Access snapshot in widget
FirestoreStreamBuilder(
collectionReference: 'collection',
child: Text('Hurray you got snapshot !'),
noDataChild: Container(),
onDataReceived:(snapshot){
//TODO your task with snapshot
}
)
I have a question about flutter_redux.
I have seen that there are two ways for passing a function to a presentational component by StoreConnector:
with typedef
as a viewmodel property
What is the difference, if any, beetween this two pieces of code?
Piece 1:
class ExampleContainer extends StatelessWidget {
ExampleContainer({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, _ViewModel>(
converter: _ViewModel.fromStore,
builder: (context, vm) {
return ExampleScreen(
exampleAction: vm.exampleAction,
);
},
);
}
}
class _ViewModel {
final Function() exampleAction;
_ViewModel({this.exampleAction});
static _ViewModel fromStore(Store<AppState> store) {
return _ViewModel(exampleAction: () {
store.dispatch(ExampleAction());
}
);
}
}
Piece 2:
typedef ExampleCallback = Function();
class ExampleContainer extends StatelessWidget {
ExampleContainer({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, ExampleCallback>(
converter: (Store<AppState> store) {
return () {
store.dispatch(ExampleAction());
};
},
builder: (BuildContext context, ExampleCallback exampleCallback) {
return ExampleScreen(
exampleAction: exampleCallback,
);
},
);
}
}
Using a ViewModel allows you to pass through more than one object from your converter.