I am making movie maker app where i am showing progress hud when creating movie from list of videos.But Progress HUD progress indicator UI stuck at starting point. Following is my code:
class _MyHomePageState extends State<MyHomePage> {
static List<dynamic> videosPath = List();
static const MethodChannel methodChannel =
const MethodChannel('moviemaker.devunion.com/movie_maker_channel');
String _batteryLevel = 'Battery level: unknown.';
ProgressHUD _progressHUD;
bool _loading = false;
#override
void initState() {
super.initState();
// _getBatteryLevel();
_progressHUD = new ProgressHUD(
backgroundColor: Colors.black12,
color: Colors.white,
containerColor: Colors.blue,
borderRadius: 5.0,
text: 'Loading...',
loading: false,
);
}
#override
Widget build(BuildContext context) {
Widget child = new Scaffold(
appBar: new AppBar(
title: new Text('Movie Maker'),
actions: <Widget>[
// action button
IconButton(
icon: Icon(Icons.movie_creation),
onPressed: () {
_createMovieSync(videosPath);
},
)
],
),
body: Stack(children: [_progressHUD, _buildContentSection()]),
floatingActionButton: new FloatingActionButton(
onPressed: () {
debugPrint("Bipin - FAB pressed");
pickVideos().asStream().listen(_setResults);
},
tooltip: "Pick a Video",
child: new Icon(Icons.add),
),
);
return child;
}
void toggleProgressHUD() {
setState(() {
if (_loading) {
_progressHUD.state.dismiss();
} else {
_progressHUD.state.show();
}
_loading = !_loading;
});
}
void _createMovieSync(List<dynamic> paths) {
toggleProgressHUD();
_createMovie(videosPath).then((moviePath) {
debugPrint("Bipin - _createMovieSync Movie created path: $moviePath");
_startMovie(moviePath).then((_) {
debugPrint("Bipin - start movie then");
}).catchError((error) {
debugPrint("Bipin - start movie error:");
}).whenComplete(() {
debugPrint("Bipin - start movie completed.");
toggleProgressHUD();
});
}).catchError((error) {
debugPrint("Bipin - Create movie error: ${error.toString()}");
}).whenComplete(() {
debugPrint("Bipin - Create movie completed.");
});
}
Future<String> _createMovie(List<dynamic> paths) {
return Future<String>(() {
debugPrint("Bipin - Create movie future going to sleep");
sleep(Duration(seconds: 10));
debugPrint("Bipin - Create movie future wake up");
return "FilePath goes here";
});
// return methodChannel.invokeMethod('createMovie', {"videoPaths": paths});
}
Future<Null> _startMovie(String moviePath) async {
return Future<Null>(() {
debugPrint("Bipin - Start movie future going to sleep");
sleep(Duration(seconds: 10));
debugPrint("Bipin - Start movie future wake up");
});
// debugPrint("Bipin - Created Movie path: $moviePath");
// return methodChannel.invokeMethod('startMovie', {"moviePath": moviePath});
}
Here, I am starting movie creation when a user taps on Create Movie action button and showing progress HUD but it is not working as expected, It's showing just like here.
Updated code to use modal_progress_hud 0.0.6, As suggested in answer But it's still, showing hanging progress hud. Find code here: https://github.com/bipinvaylu/MovieMaker-Flutter/commit/43b317efc9fdbc6e67cf36b16e4350587bf517ae
I take a slightly different approach with the modal_progress_hud package which wraps the widget you wish to make modal (with a progress indicator). After the async call returns you turn off the progress indicator. You can then play the movie.
No matter which package you use, I would probably switch to another widget to play the movie triggered by a call to setState() after making a call to stop the progress indicator. Playing the movie before the original async call completes may be preventing the progress indicator from stopping. Food for thought.
Related
I add a event to bloc immediately after pop a CupertinoModalPopup. But yield a state and not received in bloc listener. The code is like below.There are more states and events, and it's all working, except this state is not received. I don't know why, maybe it's because I add the event after the showCupertinoModalPopup was pop. I am not sure. I didn't paste the whole codes here, I only kept the relevant code. I did set the breakpoint on yield ProfileUploadAvatarSucceed(user), and it stops here.
BlocBuilder<ProfileBloc, ProfileState>(
builder: (context, state) {
return GestureDetector(
onTap: () async {
final file = await showCupertinoModalPopup(
context: context,
builder: (popupContent) {
return CupertinoActionSheet(
actions: [
CupertinoActionSheetAction(
child: Text('Photo Library'),
onPressed: () async {
final imageFile = await _picker.getImage(
source: ImageSource.gallery,
imageQuality: 70,
maxWidth: 480);
if (imageFile != null) {
Navigator.of(context).pop(imageFile);
}
},
),
],
);
});
BlocProvider.of<ProfileBloc>(context).add(UploadAvatarEvent(file));
};
},
listener: (_, state) {
if (state is ProfileLoadingState) {
BotToast.showLoading();
} else if (state is ProfileUploadAvatarSucceed) {
BotToast.closeAllLoading();
BotToast.showText(text: 'Avatar updated');
}
}
);
ProfileBloc.dart
#override
Stream<ProfileState> mapEventToState(
ProfileEvent event,
) async* {
if (event is UploadAvatarEvent) {
yield ProfileLoadingState();
await _apiProvider.updateUser(currentUser.id, payload);
final user = await _apiProvider.getUser(currentUser.id);
yield ProfileUploadAvatarSucceed(user);
}
}
Because I had problems with Google Maps and Bitmap descriptor, I tried to preload the icons before loading the Map, like this:
Widget preloadPinsAndLoadSearchMap() {
final config = ImageConfiguration(size: Size(48, 48));
final activePin = 'assets/active-pin.png';
final neutralPin = 'assets/neutral-pin.png';
final home = 'assets/home-map.png';
return FutureBuilder(
future: Future.wait([
BitmapDescriptor.fromAssetImage(config, activePin),
BitmapDescriptor.fromAssetImage(config, neutralPin),
BitmapDescriptor.fromAssetImage(config, home),
]),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return SearchMap(pins: snapshot.data);
} else {
return Container();
}
},
);
}
And the SearchMap and the Markers:
// Each Marker is produced like this:
Marker createMarker(dynamic seller) {
return Marker(
markerId: MarkerId(seller.id),
position: LatLng(seller.latitude, seller.longitude),
onTap: () => onMarkerTap(seller),
// The icons are coming from the FutureBuilder.
icon: isActive ? activePin : neutralPin,
);
}
class SearchMap extends StatelessWidget {
Widget build(BuildContext context) {
final paris = initialPosition ??
LatLng(parisPosition.latitude, parisPosition.longitude);
return GoogleMap(
myLocationButtonEnabled: false,
zoomControlsEnabled: false,
compassEnabled: false,
mapType: MapType.normal,
markers: markers,
initialCameraPosition: CameraPosition(target: paris, zoom: 15),
onMapCreated: onMapCreated,
onTap: (obj) => print(obj),
);
}
}
Happily it works on my devices very well, both iOS and Android. But on my reviewers devices, the icons are just not showing up. Is it a correct way to handle BitmapDescriptor? Maybe I’m wrong on the way to do this?
I have a screen with some informations about the user.
In my build widget I have this:
Column
(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>
[
Text('User Points', style: TextStyle(color: Colors.blueAccent)),
Text('${this.user.points}', style: TextStyle(color: Colors.black, fontWeight: FontWeight.w700, fontSize: 34.0))
],
),
And I have a function to update the dashboard each time user access it.
void updateDashboard() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
var api_key = prefs.getString('api_key');
var userID = prefs.getInt('userID');
var res = await http.post(('http://myApp.com/getDashboardPreferences/'+userID.toString() + '/' + api_key),
headers: {
"Accept": "application/json",
"content-type": "application/json"
});
this.user = User.fromJson(json.decode(res.body));
setState(() {
if (res.statusCode == 200) {
this.user.setPoints(this.user.points);
} else {
print("Something is wrong");
}
});
}
And in initstate I call that function:
#override
void initState() {
updateDashboard();
super.initState();
}
This works, but however, I get that ${this.user.points} is null for a few seconds (I get that warning screen) and then it is loaded and everything is fine. I guess it is because my function is async... But how can I assure that this updateDashboard() is called before rendering Widget build() ?
If you don't want to use FutureBuilder then you can do something like
if(done)
Column( /* whatever */ )
else
Center(child: CircularProgressIndicator())
void updateDashboard() async{
/* whatever */
if (res.statusCode == 200) {
setState(() {
this.user.setPoints(this.user.points);
done = true;
});
}
#override
void initState() {
/* whatever */
done = false;
}
Well, async means that the data might take time to load and is handed off to another thread to load. There can be multiple non blocking ways to do this:
Provide a default value for the user object, so by the time the async method finishes, you show proxy/default values and setState() makes sure the new value gets displayed when loaded.
This method requires you to load widgets conditionally, i.e load Text widget when there's data else load a Container().
Example for the second approach:
this.user!=null?Text('${this.user.points}'):Container()
This way you don't need to show the widget till the value loads. Either of the approaches work and depending on your use-case, you can choose which you can apply.
You can put the function right before the return widget.
You can make a loading variable like bool _loading then anywhere you start loading you async function you can setState of loading to true then setState of loading to false when async function stops loading like this.
void updateDashboard() async{
setState(() {
_loading = true;
});
SharedPreferences prefs = await SharedPreferences.getInstance();
var api_key = prefs.getString('api_key');
var userID = prefs.getInt('userID');
var res = await http.post(('http://myApp.com/getDashboardPreferences/'+userID.toString() + '/' + api_key),
headers: {
"Accept": "application/json",
"content-type": "application/json"
});
this.user = User.fromJson(json.decode(res.body));
setState(() {
if (res.statusCode == 200) {
this.user.setPoints(this.user.points);
} else {
print("Something is wrong");
}
_loading = false; //Set Loading to false
});
}
Then in your widget build, check for when loading is true and show a progress indicator like this.
body: _loading
? Center(
child: CircularProgressIndicator( //Show a Circular Progress indicator
valueColor: new AlwaysStoppedAnimation<Color>(Colors.cyan),
),
)
:Column(//Show your Dashboard)
I hope it helps
I need to fetch data from three different url before page rendering. So,
this is the method inside my ScopedModel includes multiple http.post methods:
Future fetchData() async {
_isLoading = true;
notifyListeners();
await fetchAvailable();
await fetchOnProgress();
await fetchCompleted();
_isLoading = false;
notifyListeners();
}
The methods inside of fetchData area just classic http.post request with raw Future type.
This is my FutureBuilder:
FutureBuilder(
future: model.fetchData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.connectionState != ConnectionState.done
? Center(
child: CircularProgressIndicator(),
)
: Column.......
The problem is, the 'future' function, it's executes constantly and never ends. My algorithm about, fetching the json from server, inflates the variables within body into ListView.builder descendants.
The output is as I said recursive post requests. Also, I'm getting this logs, the number of lines increments like 1 - 2 - 3 or 2 - 4 - 6 -8 etc.
uid=10085(my.package.directory) 1.ui identical 1 lines
.......Other logs here
uid=10085(my.package.directory) 1.ui identical 3 lines
And goes on like that...
Also, is there any other useful way to handle that small amount of data before page rendering?
Do not make HTTP requests in FutureBuilder
https://docs.flutter.io/flutter/widgets/FutureBuilder-class.html
The future must have been obtained earlier, e.g. during
State.initState, State.didUpdateConfig, or
State.didChangeDependencies. It must not be created during the
State.build or StatelessWidget.build method call when constructing the
FutureBuilder. If the future is created at the same time as the
FutureBuilder, then every time the FutureBuilder's parent is rebuilt,
the asynchronous task will be restarted.
Here is a possible right solution. This makes your data load just one time inside scaffold. First make sure you are using stateful widget.
Define your data:
Future<List<Data>> futureList;
Make a function to fetch your data from backend:
Future<void> getList() async {
final provider = Provider.of<DataProvider>(context, listen: false);
final list = provider.getData();
setState((){
futureList = list;
});
}
Load your function in init state:
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, () {
getList();
});
}
Call your fetched data in future builder:
Widget _body(BuildContext context) {
return FutureBuilder(
future: futureList,
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
if (snapshot.hasData) {
if(snapshot.data.length == 0){
return Center(child: Text('List is empty'));
}else {
return ListView.builder(
padding: EdgeInsets.only(left: 5, right: 5, top: 5, bottom: 5),
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
child: DataItem(
index: index,
dataClass: snapshot.data[index],
),
onTap: () {});
},
itemCount: snapshot.data.length);
}
} else {
return Center(child: CircularProgressIndicator());
}
},
);
I have a situation where I need to programmatically focus on a InputField(such as in response to a button press).
I'm using the Focus.moveTo function; however, even though the InputField is focused (blinking cursor appears) the keyboard is not brought up.
It seems like the best solution would be to call the RequestKeyboard() function in _InputFieldState, but this is private.
What would be the best way to accomplish this?
Here is a code sample showing the workflow:
class InputFieldWrapper extends StatefulWidget {
#override
_InputFieldWrapperState createState() => new _InputFieldWrapperState();
}
class _InputFieldWrapperState extends State<InputFieldWrapper> {
InputValue _currentInput = new InputValue(text: 'hello');
// GlobalKey for the InputField so we can focus on it
GlobalKey<EditableTextState> _inputKey = new GlobalKey<EditableTextState>();
#override
Widget build(BuildContext context) {
return new Column(
children: [
// Button that should focus on the InputField when pressed
new IconButton(
icon: new Icon(Icons.message),
onPressed: () {
Focus.moveTo(_inputKey);
},
),
// InputField that should be focused when pressing the Button
new InputField(
value: _currentInput,
key: _inputKey,
onChanged: (InputValue input) {
setState(() {
_currentInput = input;
});
}
),
],
);
}
}
This was decided to be a bug and is being tracked at #7985.