How to fix the getter length was called on null in Flutter - flutter

I'm getting the NoSuchMethodError: The gettter 'length' was called on null so just wondering how to fix this issue.
The issue happend when I try to get the length of the favorite value.
Favorite View Model
class FavoriteViewModel extends BaseViewModel {
List<FavoriteModel> favorites = [];
void initialize(FavoriteService favProvider) {
favorites = favProvider.getFavorites();
}
}
Reorder Screen
class _ReorderPageState extends State<ReorderPage> {
#override
Widget build(BuildContext context) {
var favProvider = Provider.of<FavoriteService>(context, listen: true);
return BaseView<FavoriteViewModel>(onModelReady: (model) {
model.initialize(favProvider);
}, builder: (context, model, child) {
return model.state == ViewState.Busy
......
Widget reorderWidget(FavoriteViewModel model, BuildContext bcontext) {
return Theme(
data: ThemeData(primaryColor: Colors.transparent),
child: ReorderableListView(
onReorder: (int oldIndex, int newIndex) {
_onParentReorder(oldIndex, newIndex, model);
},
scrollDirection: Axis.vertical,
children: List.generate(
model.favorites.length, // I think the issue is in this line
(index) {
FavoriteModel favorite = model.favorites[index]; // I think the issue is in this line

Did you already try to use elvis operator (similar to typescript and kotlin) ?
model?.favorites?.length
and also, its possible in your viewModel initializer favProvider.getFavorites() is always null ??

Related

Flutter Riverpod StateNotifier initialize state is empty but whenever buildMethod rebuild it's not empty

I got a HiveBox and I want to access it with Riverpod StateNotifier.
This is how I defined this provider:
final hiveSalonProvider =
StateNotifierProvider<HiveSalonNotifier, List>((ref) {
return HiveSalonNotifier();
});
Then i created a StateNotifier class which it's listening to list of SalonModel class.
class HiveSalonNotifier extends StateNotifier<List<SalonModel>> {
HiveSalonNotifier([List<SalonModel>? state])
: super(state ?? <SalonModel>[]) {
_cacheManager = SalonCacheManager('boxB');
fetchDatasFromHiveBox();
}
late final CacheManagerBase<SalonModel> _cacheManager;
List<SalonModel>? salonItems = [];
Future<void> fetchDatasFromHiveBox() async {
await _cacheManager.init();
if (_cacheManager.getValues()?.isNotEmpty ?? false) {
state = _cacheManager.getValues()!;
salonItems?.addAll(state);
print('provider: datas from caches');
} else {
print('provider:provider: datas from services');
}
}
It seems there is no error. I think so there is not.
But in UI (StatelessWidget);
In build method, I have defined our provider:
var hive = ref.read(hiveSalonProvider.notifier);
In Column:
(hive.salonItems?.isNotEmpty ?? false)
? ListView.builder(
shrinkWrap: true,
itemCount: hive.salonItems?.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: const CircleAvatar(),
title: Text(
'${hive.salonItems?[index].salonName.toString()}'),
);
},
)
: const CircularProgressIndicator(color: Colors.white),
At first hot reload, this widget showing me CircularProgressIndicator. But then I press the save code combination from keyboard (CTRL+S in vscode), it's showing listView correctly.
Where is the problem ?

UI is not updated after replacing an item in list when using notifyListeners()

I'm using the Provider package for state management in a Flutter app and I have a list model extending ChangeNotifier.
In the list model there is a method to replace a certain element in the list like this:
class MyListModel extends ChangeNotifier {
List<MyListItem> _myList = [];
void replace(Data data) {
int index = _findById(data.id);
if(index == -1) {
return;
}
_myList[index] = MyListItem(data);
log("After replace: " + _myList.toString());
notifyListeners();
}
void add(MyListItem myItem) {
_myList.add(myItem);
notifyListeners();
}
void remove(MyListItem myItem) {
_myList.remove(myItem);
notifyListeners();
}
}
This is the lis and the list item class where the provider is consumed:
class _MyListView extends StatelessWidget {
final Data _data;
const _SelectUpcomingMealList(this.upcomingMeal);
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, index) {
return MyListItem(_data);
}
);
}
}
class MyListItem extends StatelessWidget {
final Data _data;
MyListItem(this._data);
#override
Widget build(BuildContext context) {
return Consumer<MyListModel>(
builder: (context, myListModel, children) => ListTile(
title: Text(_data.name),
subtitle: Text(_data.description),
trailing: const Icon(Icons.add),
onTap: () => replaceMyItem(myListModel, context),
)
);
}
void replaceMyItem(MyListModel myListModel, BuildContext context) {
myListModel.replace(_data);
Navigator.pop(context);
}
}
For some reason the UI is not updating and the replaced item is not displayed, the old item is visible. The logging shows that the list is properly updated (the index also properly calculated), the replaced element is there, but the UI does not update.
The add() and remove() methods work, in these cases the UI properly reflects the change.
Is there something I'm missing in case of an item being replaced?

Flutter Expansion Pannel not Expanding without immutable bool

I have an expansion panel in _buildCategoryListings() that does not expand when the header or the dropdown button is clicked. isExpanded is set to the boolean categoryView.isExpanded. Through printing via the console I can see that the setState is actually updating the bool value but it looks like the actual widget isn't being redrawn perhaps? If I manually set isExpanded to true I see the results I want from the GUI. I also had set isExtended to theExpanded (which is in MovieListingView) which raises the issue of a mutable variable being in a class that extends StatefulWidget, this did give me the desired results though.
The question: How do I get the expansion panel to update the categoryView.isExpanded (via theListings[panelIndex].isExpanded) bool and show it via the GUI?
Thank you in advance.
Side note I thought about using a provider to keep track of this bool but that seems like overkill.
class MovieListingView extends StatefulWidget {
#override
_MovieListingView createState() => _MovieListingView();
MovieListingView(this.movieList);
final MovieCatalog movieList;
//bool theExpanded = false;
List<MovieCategoryView> generateCategoryList() {
List<MovieCategoryView> tempList = [];
List<String> movieCategories = movieList.Categories;
movieCategories.forEach((category) {
MovieCategoryView categoryView = new MovieCategoryView(
movieCategoryName: category.toString(),
movieList: movieList.getMovieCardListByCategory(category));
tempList.add(categoryView);
});
return tempList;
}
}
class _MovieListingView extends State<MovieListingView> {
Widget build(BuildContext context) {
// TODO: implement build
return SingleChildScrollView(
physics: ScrollPhysics(),
padding: EdgeInsets.all(5.0),
child: _buildCategoryListings(),
);
}
List<MovieCategoryView> generateCategoryList() {
List<MovieCategoryView> tempList = [];
List<String> movieCategories = widget.movieList.Categories;
int counter = 0;
movieCategories.forEach((category) {
MovieCategoryView categoryView = new MovieCategoryView(
movieCategoryName: category.toString(),
movieList:
widget.movieList.getMenuItemCardListByCategory(category),
isExpanded: false);
tempList.add(categoryView);
});
return tempList;
}
Widget _buildCategoryListings() {
final List<MovieCategoryView> theListings = generateCategoryList();
return ExpansionPanelList(
expansionCallback: (panelIndex, isExpanded) {
setState(() {
theListings[panelIndex].isExpanded = !isExpanded;
//widget.theExpanded = !isExpanded;
});
},
children: theListings.map((MovieCategoryView movieCategoryView) {
return ExpansionPanel(
canTapOnHeader: true,
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text(movieCategoryView.movieCategoryName),
);
},
body: Column(
children: movieCategoryView.movieList,
),
isExpanded: movieCategoryView.isExpanded);
}).toList(),
);
}
}
class MovieCategoryView {
MovieCategoryView(
{#required this.movieCategoryName,
#required this.movieList,
this.isExpanded});
String movieCategoryName;
List<MovieCard> movieList;
bool isExpanded = false;
}
This is happening because whenever the setstate() is called whole widget tree is rebuild and thus when you try changing the isexpandable value ,is gets changed but the
function generateCategoryList(); again gets called and generates the previous list again and again.
Widget _buildCategoryListings() {
final List<MovieCategoryView> theListings = generateCategoryList();
To fix this call the generateCategoryList(); once in initState() and remove the line above line.

The getter 'length' was called on null. Receiver: null. How to solve this error for the list of lists?

i have got list of lists and I need to retrieve data from them. I am using Provider for fetching data from the API. So I screated the ExpansionTile with 2 Listview.builder, because I have read that for retrieving data I need to use some loop for each list, e.g Listview.builder. But now it gives me the error
"The getter 'length' was called on null.
Receiver: null
Tried calling: length"
But when I use print the array isn't null, so I don't get it why I getting this error.
My code is:
class _StopScreensState extends State<StopScreens> {
List<Stop> stop;
List<Routes> routes;
List <Arrival> arrivals;
#override
void didChangeDependencies() {
stop = Provider.of<List<Stop>>(context).toList();
routes = Provider.of<List<Routes>>(context).toList();
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
//The list of lists
Iterable<Common> merge(List<Arrival> arrivals, List<Stop> stop,
List<Routes> route) sync* {
for (int i = 0; i < arrivals.length; i++) {
var fishingTackle = routes.where((v) => v.mrId == arrivals[i].mrId).toList();
var fishBait = stop.where((v) => v.stId == arrivals[i].stId).toList();
yield Common(
id: arrivals[i].mrId,
typeId: arrivals[i].mrId,
fishingTackle: fishingTackle,
fishBait: fishBait,
time: arrivals[i].taArrivetime,
);
}
}
//the common list for all arrays
List<Common> common = merge(arrivals, stop, routes).toList();
return Scaffold(
appBar: AppBar(
title: Text('Остановки'),
),
body: Provider.value(value: common) == null
? Center(
child: CircularProgressIndicator(),
)
: ListView.builder(
itemCount: common.length,
itemBuilder: (context, index) {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ExpansionTile(title: Text(common[index].fishingTackle[index].mrTitle),
children: [
ListView.builder(itemCount: stop.length,itemBuilder: (context, index){
return ListTile(
title: Text(common[index].fishBait[index].stTitle),
leading: Text(common[index].time.toString()),
);
It's because you have a null list.
Try to always initialized your list with an empty list, so you don't need to handle the null value for each list.
Change:
List<Stop> stop;
List<Routes> routes;
List <Arrival> arrivals;
to
List<Stop> stop = [];
List<Routes> routes = [];
List <Arrival> arrivals = [];
It seems you're not assigning anything to arrivals

DragTarget widget is not responding

I am coding a chess game in flutter.
and this is the relevant bits of my code :
class Rank extends StatelessWidget {
final _number;
Rank(this._number);
#override
Widget build(BuildContext context) {
var widgets = <Widget>[];
for (var j = 'a'.codeUnitAt(0); j <= 'h'.codeUnitAt(0); j++) {
widgets
.add(
DroppableBoardSquare(String.fromCharCode(j) + this._number.toString())
);
//
}
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: widgets);
}
}
class DroppableBoardSquare extends StatelessWidget {
final String _coordinate;
const DroppableBoardSquare(this._coordinate) ;
#override
Widget build(BuildContext context) {
return DragTarget(
builder:(BuildContext context, List candidate, List rejectedData){
return BoardSquare(_coordinate);
},
onAccept: (data ) {
print('Accepted');
},
onWillAccept: (data){
return true;
},
onLeave: (data) => print("leave"),);
}
}
class BoardSquare extends StatelessWidget {
final String _coordinate;
BoardSquare(this._coordinate);
#override
Widget build(BuildContext context) {
ChessBloc bloc = ChessBlocProvider.of(context);
return
StreamBuilder<chess.Chess>(
stream: bloc.chessState,
builder: (context, AsyncSnapshot<chess.Chess> chess) {
return DraggablePieceWidget(chess.data.get(_coordinate), _coordinate);
});
}
}
class DraggablePieceWidget extends StatelessWidget {
final chess.Piece _piece;
final String _coordinate;
DraggablePieceWidget(this._piece, String this._coordinate);
#override
Widget build(BuildContext context) {
return Draggable(
child: PieceWidget(_piece),
feedback: PieceWidget(_piece),
childWhenDragging: PieceWidget(null),
data: {"piece": _piece, "origin": _coordinate} ,
);
}
}
Now the problem is that I can drag the piece fine, but cannot drop them. None of the methods on DragTarget is getting called.
what I am doing wrong?
I developed a drag-n-drop photos grid, where you can drag photos to reorder them based on numeric indexes.
Essentially, I assume, it is the same thing as the chessboard concept you have.
The problem possibly occurs due to Draggable (DraggablePieceWidget) element being inside of DragTarget (DroppableBoardSquare).
In my app I made it the other way around - I placed DragTarget into Draggable.
Providing some pseudo-code as an example:
int _dragSelectedIndex;
int _draggingIndex;
// Basically this is what you'd use to build every chess item
Draggable(
maxSimultaneousDrags: 1,
data: index,
onDragStarted: () { _draggingIndex = index; print("Debug: drag started"); }, // Use setState for _draggingIndex, _dragSelectedIndex.
onDragEnd: (details) { onDragEnded(); _draggingIndex = null; print("Debug: drag ended; $details"); },
onDraggableCanceled: (_, __) { onDragEnded(); _draggingIndex = null; print("Debug: drag cancelled."); },
feedback: Material(type: MaterialType.transparency, child: Opacity(opacity: 0.85, child: Transform.scale(scale: 1.1, child: createDraggableBlock(index, includeTarget: false)))),
child: createDraggableBlock(index, includeTarget: true),
);
// This func is used in 2 places - Draggable's `child` & `feedback` props.
// Creating dynamic widgets through functions is a bad practice, switch to StatefulWidget if you'd like.
Widget createDraggableBlock(int index, { bool includeTarget = true }) {
if (includeTarget) {
return DragTarget(builder: (context, candidateData, rejectedData) {
if (_draggingIndex == index || candidateData.length > 0) {
return Container(); // Display empty widget in the originally selected cell, and in any cell that we drag the chess over.
}
// Display a chess, but wrapped in DragTarget widget. All chessboard cells will be displayed this way, except for the one you start dragging.
return ChessPiece(..., index: index);
}, onWillAccept: (int elemIndex) {
if (index == _draggingIndex) {
return false; // Do not accept the chess being dragged into it's own widget
}
setState(() { _dragSelectedIndex = index; });
return true;
}, onLeave: (int elemIndex) {
setState(() { _dragSelectedIndex = null; });
});
}
// Display a chess without DragTarget wrapper, e.g. for the draggable(feedback) widget
return ChessPiece(..., index: index);
}
onDragEnded() {
// Check whether _draggingIndex & _dragSelectedIndex are not null and act accordingly.
}
I assume if you change index system to custom objects that you have - this would work for you too.
Please let me know if this helped.