Search code examples
flutterdart-null-safetyriverpodflutter-riverpod

Flutter Null check operator used on a null value even after checking conditionally for null values


I have a Flutter + Riverpod project in which I am fetching some data when a screen loads. Getting the Null check operator error but I am checking if the value is null or not beforehand. State has a nullable field currentlyReading which is null at the beginning. Function to fetch data is being called in the constructor of the controller. The state is being updated correctly after fetching. Trying to render UI conditionally based on the result of fetch but getting the error.

Controller that fetches data and manages state:

final bookControllerProvider = StateNotifierProvider<BookController, BookState>(
    (ref) => BookController(ref.read(bookRepositoryProvider), ref));

class BookState {
  final Book? currentlyReading;
  final List<Book>? books;
  final bool isLoading;
  final String? error;

  BookState(
      {this.currentlyReading,
      this.books = const [],
      this.isLoading = true,
      this.error});

  BookState copyWith({
    Book? currentlyReading,
    List<Book>? books,
    bool? isLoading,
    String? error,
  }) {
    return BookState(
      currentlyReading: currentlyReading ?? this.currentlyReading,
      books: books ?? this.books,
      isLoading: isLoading ?? this.isLoading,
      error: error ?? this.error,
    );
  }
}

class BookController extends StateNotifier<BookState> {
  final BookRepository _bookRepository;
  final Ref _ref;

  BookController(this._bookRepository, this._ref) : super(BookState()) {
    getCurrentlyReading();
  }

  void getCurrentlyReading() async {
    state = state.copyWith(isLoading: true);
    final user = _ref.read(userProvider);
    final book = await _bookRepository.getBook(user!.readingBook!);
    book.fold((l) {
      state = state.copyWith(error: l.message, isLoading: false);
    }, (userBook) {
      state = state.copyWith(currentlyReading: userBook, isLoading: false);
    });
  }
}

Using in UI:

    final user = ref.watch(userProvider)!;

    final bookData = ref.watch(bookControllerProvider);

    return Scaffold(
        body: SafeArea(
      child: Padding(
        padding:
            const EdgeInsets.only(top: 16.0, right: 16, bottom: 8, left: 16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            
            const SizedBox(height: 16),
            Text(
              'Currently Reading',
              style: AppStyles.bodyText.copyWith(
                  fontSize: 18,
                  fontWeight: FontWeight.w500,
                  color: Pallete.textGrey),
            ),
            const SizedBox(height: 16),
            bookData.isLoading
                ? const Expanded(child: Center(child: Loader()))
                : bookData.currentlyReading == null
                    ? const Text('Some error occurred')
                    : BookInfo(
                        book: bookData.currentlyReading!, height: deviceHeight)
          ],
        ),
      ),
    ));

BookInfo:

class BookInfo extends StatelessWidget {
  final Book book;
  final double height;

  const BookInfo({
    Key? key,
    required this.book,
    required this.height,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        Image(
          image: NetworkImage(book.thumbnail!),
        ),
        SizedBox(
          height: height * 0.01,
        ),
        Text(book.title,
            style: AppStyles.subtext.copyWith(
                color: Pallete.primaryBlue, fontWeight: FontWeight.w500)),
        Text('by ${book.authors.join(', ')}', style: AppStyles.bodyText),
      ],
    );
  }
}

However, a simple Text widget like below works when used in place of BookInfo

Text(bookData.currentlyReading!.title)

Solution

  • Your problem is not because of bookData.currentlyReading its happened in BookInfo widget when try to build Image widget, book.thumbnail may be null and you use ! on it:

    book.thumbnail == null ? SizedBox() : Image(
          image: NetworkImage(book.thumbnail!),
    ),