Flutter memory leak with listview builder - flutter

I'm new to flutter and have a memory leak in my program. I was wondering if anyone jumped out to anyone about my code as to why it might be causing a memory leak. I was hoping I made some sort of newbie mistake that someone could call out. I commented out my listviewbuilder row widget and replaced it with simple text, and that seemed to lessen the memory leak. So it seems like it might be row related, but I don't understand how.
I am not scrolling or anything like that I just fire up the memory profiler. It shows that there is an ever-increasing size in the filtered node, but does not say why.
I went back through my changesets and found some code that seemed to be causing the issues. Specifically the memory leak went away when I rmeoveLoadingAnimationWidget:
AnimatedOpacity(
opacity: isUpdating ? 1.0 : 0.0,
duration: isUpdating
? const Duration(milliseconds: 0)
: const Duration(milliseconds:500),
child: Row(
children: [
LoadingAnimationWidget.fourRotatingDots(
color: Colors.black12,
size: 20,
),
SizedBox(
width: 3,
),
Text(
"Saving...",
style: TextStyle(color: Colors.grey.shade600),
),
],
),
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:save_extra_money/main.dart';
import 'package:save_extra_money/shared/loading/LoadingIndicator.dart';
import 'package:save_extra_money/transactions/TransactionUpdaterProvider.dart';
import 'package:save_extra_money/transactions/TransactionsLoadingStates.dart';
import 'package:save_extra_money/transactions/transactionGridRow.dart';
import 'package:http/http.dart' as http;
import '../Shared/ColorContainer.dart';
import '../model/Transaction.dart';
import '../model/webCallInputsAndOutputs/RetrieveTransactionsResults.dart';
import '../shared/globals/GlobalUi.dart';
import 'TransactionsViewModel.dart';
class Transactions extends ConsumerStatefulWidget {
Transactions({Key? key}) : super(key: key);
#override
TransactionsState createState() => TransactionsState();
}
class TransactionsState extends ConsumerState<Transactions> {
ScrollController? controller;
#override
void initState() {
super.initState();
controller = ScrollController()..addListener(_scrollListener);
}
#override
void dispose() {
if (controller != null) {
controller!.removeListener(_scrollListener);
}
super.dispose();
}
void _scrollListener() {
if (controller == null) {
return;
}
// print(controller.position.extentAfter);
if (controller!.position.extentAfter < 300) {
ref.read(transactionsViewModel.notifier).addMorePagedData();
}
}
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
var transactionUpdater = ref.watch(transactionUpdaterProvider);
var viewModel = ref.watch(transactionsViewModel);
viewModel.loadInitialData();
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(viewModel.getTitle()),
backgroundColor: GlobalUI.appBar,
centerTitle: true,
),
body: ColorContainer(
size: size,
child: ClipRRect(
borderRadius: BorderRadius.circular(5.0),
child:
getInitialLayout(viewModel, transactionUpdater),
),
),
);
}
Widget getInitialLayout(TransactionsViewModel viewModel,
TransactionUpdaterProvider transactionUpdater){
if(viewModel.state == TransactionsLoadingStates.InitialLoad){
return LoadingIndicator(text: 'Transactions...');
}
else if(viewModel.state == TransactionsLoadingStates.InitialError) {
return Text('Error: ${viewModel.errorText}');
}
else{
return ListView.builder(
controller: controller,
itemCount: viewModel.totalCount,
itemBuilder: (BuildContext context, int index) {
if(index==viewModel.transactions.length){
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5),
bottomRight: Radius.circular(5)
),
color: GlobalUI.rowBackground,
),
child: LoadingAnimationWidget.fourRotatingDots(
color: Colors.black12,
size: 60,
),
);
}
if(index>viewModel.transactions.length){
return SizedBox(height: 0,width: 0,);
}
var transaction = viewModel.transactions[index];
return Text(transaction.id);
/*
return TransactionGridRow(
transaction: transaction,
isUpdating: transactionUpdater.isUpdating(transaction.id),
);
*/
});
}
}
}

You should not make api calls in your widget build method
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:save_extra_money/main.dart';
import 'package:save_extra_money/shared/loading/LoadingIndicator.dart';
import 'package:save_extra_money/transactions/TransactionUpdaterProvider.dart';
import 'package:save_extra_money/transactions/TransactionsLoadingStates.dart';
import 'package:save_extra_money/transactions/transactionGridRow.dart';
import 'package:http/http.dart' as http;
import '../Shared/ColorContainer.dart';
import '../model/Transaction.dart';
import '../model/webCallInputsAndOutputs/RetrieveTransactionsResults.dart';
import '../shared/globals/GlobalUi.dart';
import 'TransactionsViewModel.dart';
class Transactions extends ConsumerStatefulWidget {
Transactions({Key? key}) : super(key: key);
#override
TransactionsState createState() => TransactionsState();
}
class TransactionsState extends ConsumerState<Transactions> {
ScrollController? controller;
#override
void initState() {
super.initState();
controller = ScrollController()..addListener(_scrollListener);
viewModel.loadInitialData(); // You can call your async methods in initstate
}
#override
void dispose() {
if (controller != null) {
controller!.removeListener(_scrollListener);
}
super.dispose();
}
void _scrollListener() {
if (controller == null) {
return;
}
// print(controller.position.extentAfter);
if (controller!.position.extentAfter < 300) {
ref.read(transactionsViewModel.notifier).addMorePagedData();
}
}
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
var transactionUpdater = ref.watch(transactionUpdaterProvider);
var viewModel = ref.watch(transactionsViewModel);
viewModel.loadInitialData(); // Here is the method you should not call here
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(viewModel.getTitle()),
backgroundColor: GlobalUI.appBar,
centerTitle: true,
),
body: ColorContainer(
size: size,
child: ClipRRect(
borderRadius: BorderRadius.circular(5.0),
child:
getInitialLayout(viewModel, transactionUpdater),
),
),
);
}
Widget getInitialLayout(TransactionsViewModel viewModel,
TransactionUpdaterProvider transactionUpdater){
if(viewModel.state == TransactionsLoadingStates.InitialLoad){
return LoadingIndicator(text: 'Transactions...');
}
else if(viewModel.state == TransactionsLoadingStates.InitialError) {
return Text('Error: ${viewModel.errorText}');
}
else{
return ListView.builder(
controller: controller,
itemCount: viewModel.totalCount,
itemBuilder: (BuildContext context, int index) {
if(index==viewModel.transactions.length){
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5),
bottomRight: Radius.circular(5)
),
color: GlobalUI.rowBackground,
),
child: LoadingAnimationWidget.fourRotatingDots(
color: Colors.black12,
size: 60,
),
);
}
if(index>viewModel.transactions.length){
return SizedBox(height: 0,width: 0,);
}
var transaction = viewModel.transactions[index];
return Text(transaction.id);
/*
return TransactionGridRow(
transaction: transaction,
isUpdating: transactionUpdater.isUpdating(transaction.id),
);
*/
});
}
}
}

In addition to batuhand, Avoid this:
if(index>viewModel.transactions.length){
return SizedBox(height: 0,width: 0,);
}
Each time index is greater than length it will built a useless SizedBox.
Why do you use "viewModel.totalCount" and "viewModel.transactions.length"? It should be the same.
And if you compare index and length add a minus to length
if(index==viewModel.transactions.length-1)

Related

AddpostFramecall back Function not Call in persistent bottom navigation bar

I want to animate my list back and forth with scroll animation this FoodFragment call at the persistent bottom navigation
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:ikwte/AppConstant/StaticData.dart';
import 'package:ikwte/AppConstant/ThemeColors.dart';
import 'package:ikwte/AppUtils/AppUtils.dart';
import 'package:ikwte/ProviderControllers/FoodController.dart';
import 'package:ikwte/Widgets/FragmentWidgets/FoodHelper.dart';
import 'package:ikwte/main.dart';
import 'package:provider/provider.dart';
class FoodFragment extends StatefulWidget {
const FoodFragment({Key? key}) : super(key: key);
#override
State<FoodFragment> createState() => _FoodFragmentState();
}
class _FoodFragmentState extends State<FoodFragment> {
ScrollController _scrollController = ScrollController();
var themeColor = ThemeColors();
var utils = AppUtils();
var static = Statics();
#override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
double minScrollExtent1 = _scrollController.position.minScrollExtent;
double maxScrollExtent1 = _scrollController.position.maxScrollExtent;
animateToMaxMin(maxScrollExtent1, minScrollExtent1, maxScrollExtent1, 1,
_scrollController);
print("Function Triggered");
});
print("InitState Triggered");
}
animateToMaxMin(double max, double min, double direction, int seconds,
ScrollController scrollController) {
scrollController
.animateTo(direction,
duration: Duration(seconds: seconds), curve: Curves.linear)
.then((value) {
direction = direction == max ? min : max;
animateToMaxMin(max, min, direction, seconds, scrollController);
});
}
#override
Widget build(BuildContext context) {
FoodHelper helper = FoodHelper(context, _scrollController);
return RefreshIndicator(
backgroundColor: themeColor.yellowColor,
color: themeColor.blueColor,
onRefresh: () async {
await apisCall();
},
child: Scaffold(
backgroundColor: themeColor.blueColor,
body: Column(
children: [
utils.statusBar(context, color: themeColor.blueColor),
Expanded(
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
color: themeColor.blueColor,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
helper.appBar(),
helper.searchFieldText(),
helper.topListHeading(),
helper.topList(),
helper.whatAreYouLookingForHeading(),
helper.whatAreYouLookingForList(),
helper.collaborationsHeading(),
helper.collaborationList(),
helper.newInTownHeading(),
helper.newInTownList(),
helper.allRestaurantsHeading(),
helper.allRestaurantsList()
],
),
)),
),
utils.bottomBar(context, color: themeColor.blueColor),
],
),
),
);
}
apisCall() {
navigatorkey.currentContext!
.read<FoodController>()
.getAllRestaurantsListApi();
navigatorkey.currentContext!.read<FoodController>().getCategoryListApi();
navigatorkey.currentContext!
.read<FoodController>()
.getTopRestaurantsListApi();
navigatorkey.currentContext!
.read<FoodController>()
.getNewInTownRestaurantApi();
navigatorkey.currentContext!.read<FoodController>().getSecretMenuApi();
}
}
But the issue i am facing right now is AddpostFrameCallback Function is not calling on the persistent bottom navigation so my animation is not getting trigged i tried multiple Solutions but it didn't work for me .How can i reslove this issue

LateInitializationError in initialization of Stateful Widget

In my mobile application, I am initializing a Stateful widget from another widget but I always get an exception
[ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception:
LateInitializationError: Field '_customAppLoaderState#64195267' has
not been initialized
Below is the code for custom_loader.dart
import 'package:SMedoApp/util/app_textstyles.dart';
import 'package:SMedoApp/util/color_constants.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class CustomAppLoader extends StatefulWidget {
// const CustomAppLoader({Key? key}) : super(key: key);
late final _CustomAppLoaderState _customAppLoaderState;
#override
State<CustomAppLoader> createState() {
_customAppLoaderState=_CustomAppLoaderState();
return _customAppLoaderState;
}
void setLoaderVisible(bool _visibility){
_customAppLoaderState.setVisibility(_visibility);
}
void setProgressPerc(double progress){
_customAppLoaderState.setProgressValue(progress: progress);
}
void setCancelToken(CancelToken cancelToken) {
_customAppLoaderState.setCancelToken(cancelToken: cancelToken);
}
}
class _CustomAppLoaderState extends State<CustomAppLoader> {
bool isLoaderVisible=false;
double _progress=0.0;
CancelToken? _cancelToken;
bool isCancelButtonVisible=false;
#override
Widget build(BuildContext context) {
return Visibility(
visible: isLoaderVisible,
child: Center(
child: Container(
color: ColorConstants.black.withOpacity(0.8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SpinKitWave(
size: 50,
color: ColorConstants.white,
type: SpinKitWaveType.start
),
SizedBox(
height: 50,
),
Container(
width: 200,
child: LinearProgressIndicator(
backgroundColor: ColorConstants.white,
valueColor: new AlwaysStoppedAnimation<Color>(ColorConstants.facebook_blue),
value: _progress,
minHeight: 2,
),
),
SizedBox(
height: 10,
),
Visibility(
visible: isCancelButtonVisible,
child: TextButton(onPressed: (){
_cancelToken?.cancel();
if(_cancelToken!.isCancelled)
Navigator.pop(context);
}, child: Text(AppLocalizations.of(context)!.cancel, style: AppTextStyle.whiteOnBlackSmallWhite(context),), ),
)
],
)),
),
);
}
void setVisibility(bool _visibility){
setState(() {
isLoaderVisible=_visibility;
});
}
void setProgressValue({required double progress}) {
setState(() {
_progress=progress;
}
);
}
void setCancelToken({required CancelToken cancelToken}) {
setState(() {
_cancelToken=cancelToken;
isCancelButtonVisible=true;
});
}
}
And this is how I invoke custom_loader from another widget
CustomAppLoader loader=CustomAppLoader();
loader.setProgressPerc(0.25);
Where am I going wrong? (I am new to flutter/ dart).
createState() is not called yet on initialization of the CustomAppLoader, so when you call setProgressPerc the state doesn't exist yet. It's also not really common to save the state in a variable and using it like that.
My IDE also actually suggest that you shouldn't do any logic in the createState():

Null check operator used on a null Value ( List )

I'm trying to pass the data of a List for another screen but i don't know how to do that.
I've tried using constructors and using correctly null-safety but the app returned this error: Null check operator used on a null Value. And i'm a long time already trying to solve this problem but i don't know how. What's the benefits of null safety? Here's the code:
Code 1
import 'dart:convert';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:mood3/telas/RecebeDados.dart';
import 'package:mood3/telas/listas.dart';
import 'package:path_provider/path_provider.dart';
import 'package:toast/toast.dart';
import 'Diary.dart';
class Escrita extends StatefulWidget {
List? lista = [];
Escrita({this.lista});
#override
State<Escrita> createState() => _EscritaState();
}
class _EscritaState extends State<Escrita> {
Future<File>_getFile()async{
final diretorio = await getApplicationDocumentsDirectory();
return File("${diretorio.path}/dados.json");
}
_salvar() async {
var arquivo = await _getFile();
Map<String,dynamic> tarefa = Map();
tarefa["titulo"] = "Ir ao mercado";
tarefa["realizada"] = false;
widget.lista!.add(tarefa);
String dados = json.encode(widget.lista);
arquivo.writeAsString(dados);
}
_ler() async {
try{
final arquivo = await _getFile();
return arquivo.readAsString();
}catch(e){
return null;
}
}
#override
void initState() {
super.initState();
_ler().then((dados){
setState(() {
widget.lista = json.decode(dados);
});
});
}
TextEditingController _controllerTarefa = TextEditingController();
#override
Widget build(BuildContext context) {
ToastContext().init(context);
return Scaffold(
body: Center(
child: SingleChildScrollView(
child:Column(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16),
child: TextField(
keyboardType: TextInputType.multiline,
controller: _controllerTarefa,
decoration: InputDecoration(
hintText: "Diary Title",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Colors.blueGrey,
),
)),
Padding(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16),
child: TextField(
keyboardType: TextInputType.multiline,
maxLines: null,
decoration: InputDecoration(
hintText: "Diary Text",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Colors.blueGrey,
),
),),
],
),),
),
}
),
]
),
);
}
}
Code 2:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:mood3/telas/Account.dart';
import 'package:mood3/telas/Diary.dart';
import 'package:mood3/telas/Friends.dart';
import 'package:mood3/telas/Graph.dart';
import 'package:mood3/telas/Home.dart';
import 'package:mood3/telas/Music.dart';
import 'package:mood3/telas/PlayerDoVideo.dart';
import 'package:mood3/telas/Videos.dart';
import 'package:mood3/telas/animacao.dart';
import 'package:mood3/telas/escrita.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent-tab-view.dart';
class RecebeDados extends StatefulWidget {
#override
State<RecebeDados> createState() => _RecebeDadosState();
}
class _RecebeDadosState extends State<RecebeDados> {
Escrita flista = Escrita();
late List? list = flista.lista;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: list!.length,
itemBuilder: (context, index){
return ListTile(
title: Text( list![index]['titulo'] ),
);
}
),
)
],
),
floatingActionButton: SpeedDial(
animatedIcon: AnimatedIcons.menu_close,
backgroundColor: Colors.black,
icon: CupertinoIcons.pencil,
overlayColor: Colors.black,
children: [
Text ("teste")
]
),
);
}
}
In this case you are creating "lista" as a List that could be null (List?), but also you initialize it with value: a empty list ( [] )
List? lista = [];
But when you call the constructor of Escrita, you are giving a value to the lista variable.
Widget myWidget = Escrita(); /// lista is null
lista is null, because you are not indicating a value in the constructor.
If you want a default value this is the correct way:
Escrita({this.lista = []});
You are receiving errors because you are using list!.length and list![index]['titulo'], and using the ! you are asecuring that the list is not null, but yes, its null! So thats the reason because you are receiving the error.
You can solve your problem in this way:
class Escrita extends StatefulWidget {
List lista;
Escrita({this.lista=[]});
#override
State<Escrita> createState() => _EscritaState();
}
Or you can do this:
class Escrita extends StatefulWidget {
List lista;
Escrita({required this.lista});
#override
State<Escrita> createState() => _EscritaState();
}
Or
class Escrita extends StatefulWidget {
List lista;
Escrita(this.lista);
#override
State<Escrita> createState() => _EscritaState();
}

Unable to load cameras from android emulator in flutter

I am trying to use the camera plugin for flutter.
I call availableCameras() in the main function of the app, as I have seen in documentation like this:
List<CameraDescription> cameras;
Future<void> main() async {
try {
cameras = await availableCameras();
} catch (e) {
print("Error: $e");
}
runApp(MyApp());
}
The try statement fails instantly printing
Error: Null check operator used on a null value
I call Home() in MyApp(), which has a tabbed navigator, which calls FaceAScreen() like this:
return Scaffold(
appBar: buildAppBar(),
bottomNavigationBar: BottomNavBar(tabController: _tabController, myTabs: myTabs),
body: TabBarView(
controller: _tabController,
children: <Widget>[Body(), FaceAScreen(cameras: widget.cameras)],
),
);
I want the Camera in the FaceAScreen() page, but it shows, expectedly, "No Camera!" text, since the List cameras is null.
The code build method for FaceAScreen() is:
#override
Widget build(BuildContext context) {
if (!isDetecting) {
return Center(
child: Container(
margin: EdgeInsets.all(10),
child: Text("No cameras!"),
),
);
}
// This is not what causes the no camera to show on the page
if (!controller.value.isInitialized) {
return Container(child: Text("No cameras"));
}
return AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: CameraPreview(controller),
);
}
I have looked at the null value error relentlessly and have upgraded my flutter package from the terminal many times, including running flutter clean. Flutter doctor shows everything is fine. I also have double checked the settings on my android emulator and have front facing set to webcam0 and back facing as "emulated". I checked opening the camera app in the emulator and that works. All the tutorials seem to write use the availableCameras() method exactly as I did (without the try catch even). Any help would be great, thanks!
Recently I finished a tutorial, which uses camera and works fine. The code below can be of help to you:
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter_icons/flutter_icons.dart';
class CameraPage extends StatefulWidget {
CameraPage({Key key}) : super(key: key);
#override
_CameraPageState createState() => _CameraPageState();
}
/* ============================================================================================= */
class _CameraPageState extends State<CameraPage> {
List<CameraDescription> _cameras;
CameraController _controller;
var _isReady = false;
/* ---------------------------------------------------------------------------- */
#override
void initState() {
super.initState();
_setUpCamera();
}
/* ---------------------------------------------------------------------------- */
void _setUpCamera() async {
try {
// initialize cameras
_cameras = await availableCameras();
// initialize camera controllers
// Current bug for high/medium with Samsung devices
_controller = CameraController(_cameras[0], ResolutionPreset.medium);
await _controller.initialize();
} on CameraException catch (_) {
// do something on error
}
if (mounted) setState(() => _isReady = true);
}
/* ---------------------------------------------------------------------------- */
Widget cameraPreview() {
return AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: CameraPreview(_controller),
);
}
/* ---------------------------------------------------------------------------- */
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
floatingActionButton: getFooter(),
body: getBody(),
);
}
/* ---------------------------------------------------------------------------- */
Widget getBody() {
final size = MediaQuery.of(context).size;
var flag = !_isReady || _controller == null || !_controller.value.isInitialized;
return Container(
decoration: flag ? BoxDecoration(color: Colors.white) : null,
width: size.width,
height: size.height,
child: flag
? Center(
child: SizedBox(
width: 25,
height: 25,
child: CircularProgressIndicator(strokeWidth: 3),
),
)
: ClipRRect(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10),
),
child: cameraPreview(),
),
);
}
.....
}
Obviously your Android device emulator must have enabled cameras and SD Card.

Using video_player package with Flutter Hooks to play a background fullscreen video

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;
}