androidflutterdartmobile

Flutter null check operator used on a null value?


I work on a shop app for learning. And when I try to get data from the API, I always have this problem:

Null check operator used on a null value

And no matter what I do I keep having this error.

Here are my code blocks:

Main File:

void main() async
{
  //this line of code ensures that all the data are set and get before running the app
  WidgetsFlutterBinding.ensureInitialized();

  Bloc.observer = MyBlocObserver();
  DioHelper.init();
  await CacheHelper.init();

  Widget? widget;
  bool? isDarkTheme = CacheHelper.getData(key: 'isDark') ?? false;
  bool? onBoarding = CacheHelper.getData(key: 'onBoarding') ?? false;
  token = CacheHelper.getData(key: 'token')??'';
  print(token);

  if(onBoarding != null && onBoarding)
  {
    widget = token.isNotEmpty? ShopLayout() : ShopLoginScreen();
  }
  else {
    widget = OnBoardingScreen();
  }

  runApp(MyApp(isDarkTheme: isDarkTheme ,startWidget: widget));
}

class MyApp extends StatelessWidget
{
  final bool? isDarkTheme;
  final Widget? startWidget;

  MyApp({@required this.isDarkTheme, @required this.startWidget});
  @override
  Widget build(BuildContext context) {
      return MultiBlocProvider(
        providers:
        [
          BlocProvider(create: (BuildContext context) => ShopCubit()..changeAppMode(isDarkFromPreferences: isDarkTheme)),
          BlocProvider(create: (BuildContext context) => ShopCubit()..getHomeData()),
          BlocProvider(create: (BuildContext context) => WhatsappCubit()),
        ],
        child: BlocConsumer<ShopCubit, ShopStates>(
          listener: (context, state){},
          builder: (context, state) => MaterialApp
          (
            theme: lightTheme,
            darkTheme: darkTheme,
            themeMode: ThemeMode.light,//isDarkTheme!? ThemeMode.dark: ThemeMode.dark,
            debugShowCheckedModeBanner: false,
            home: startWidget,
          ),
        ),
      );
  }

}

Cubit File:

class ShopCubit extends Cubit<ShopStates>
{
  ShopCubit() : super(ShopInitialState());

  static ShopCubit get(context) => BlocProvider.of(context);

  int currentIndex = 0;

  bool isDark = true;
  ThemeMode? appTheme;

  List<Widget> screens =
  [
    ProductsScreen(),
    CategoriesScreen(),
    FavouritesScreen(),
    SettingsScreen(),
  ];

  void changeBottomSheetIndex(int index)
  {
    currentIndex = index;
    emit(ShopChangeBottomNavCurrentIndexState());
  }

  HomeModel? homeModel;
  void getHomeData()
  {
    emit(ShopLoadingHomeDataState());

    DioHelper.getData(path: HOME, token: token).then((value) {
      if (value.data != null) {

        homeModel = HomeModel.fromJson(value.data);
        print('Data fetched successfully');
        printFullText(homeModel!.data!.banners[0].image);
        emit(ShopSuccessHomeDataState());
      }
      else
      {
        emit(ShopErrorHomeDataState('Response data is null'));
      }
    }).catchError((error) {
      emit(ShopErrorHomeDataState(error.toString()));
      print('Error fetching data: ${error.toString()}');
    });

  }

  void changeAppMode({bool? isDarkFromPreferences})
  {
    if(isDarkFromPreferences != null)
    {
      isDark = isDarkFromPreferences;
      emit(ShopChangeAppModeState());
    }
    else
    {
      isDark = !isDark;
    }

        if(isDark)
        {
          appTheme = ThemeMode.dark;
          CacheHelper.saveData(key: 'isDark', value: isDark).then((value)
          {
            emit(ShopChangeAppModeState());
          }).catchError((error)
          {
            print('An error occurred while trying to set a new value as a shared preference');
          });
        } else
        {
          appTheme = ThemeMode.light;
          CacheHelper.saveData(key: 'isDark', value: isDark).then((value)
          {
            emit(ShopChangeAppModeState());
          }).catchError((error)
          {
            print('An error occurred while trying to set a new value as a shared preference');
          });
        }
  }
}

Dio Helper File:

class DioHelper {
  static Dio? dio;

  static void init() {
    dio = Dio(
      BaseOptions(
        baseUrl: 'https://student.valuxapps.com/api/',
        receiveDataWhenStatusError: true,
      ),
    );
  }

  static Future<Response> getData({
    @required String? path,
    Map<String, dynamic>? query,
    String lang = 'en',
    String? token,
  })
  async
  {
    dio!.options.headers = {
      'Content-Type':'application/json',
      'lang':lang,
      'Authorization':token??'',
    };

    return await dio!.get(path!, queryParameters: query!);
  }

  static Future<Response> postData({
    @required String? url,
    Map<String, dynamic>? query,
    @required Map<String, dynamic>? data,
    String lang = 'en',
    String? token,
  }) async
  {
    dio!.options.headers = {
      'Content-Type':'application/json',
      'lang':lang,
      'Authorization':token??'',
    };
    return dio!.post(url!, queryParameters: query, data: data);
  }
}

Cache Helper File:

class CacheHelper
{
  static SharedPreferences? sharedPreferences;

  static Future<void> init() async
  {
    sharedPreferences = await SharedPreferences.getInstance();
  }

  static dynamic getData({@required String? key})
  {
    if(sharedPreferences == null) {
      return 'error this value is null';
    }
    return sharedPreferences!.get(key!);
  }

  static Future<bool> saveData({
    @required String? key,
    @required dynamic value,
  }) async
  {
    if(value is String) return await sharedPreferences!.setString(key!, value);
    if(value is int) return await sharedPreferences!.setInt(key!, value);
    if(value is bool) return await sharedPreferences!.setBool(key!, value);
    return await sharedPreferences!.setDouble(key!, value);
  }

  static Future<bool> removeData({@required String? key}) async
  {
    return await sharedPreferences!.remove(key!);
  }
}

Data Receive File:

class HomeModel
{
    bool? status;
    HomeDataModel? data;

    HomeModel.fromJson(Map<String, dynamic> json)
    {
      status = json['status'];
      data = HomeDataModel.fromJson(json['data']);
    }
}

class HomeDataModel
{
  List<BannerModel> banners = [];
  List<ProductModel> products = [];

  HomeDataModel.fromJson(Map<String, dynamic> json)
  {
    json['banners'].forEach((element)
    {
      banners.add(element);
    });

    json['products'].forEach((element)
    {
      products.add(element);
    });
  }
}

class BannerModel
{
  int? id;
  String? image;

  BannerModel.fromJson(Map<String, dynamic> json)
  {
    id = json['id'];
    image = json['image'];
  }
}

class ProductModel
{
  int? id;
  dynamic price;
  dynamic oldPrice;
  dynamic discount;
  String? image;
  String? name;
  bool inFavourites = false;
  bool inCart = false;

  ProductModel.fromJson(Map<String, dynamic> json)
  {
    id = json['id'];
    price = json['price'];
    oldPrice = json['old_price'];
    discount = json['discount'];
    image = json['image'];
    name = json['name'];
    inFavourites = json['in_favourites'];
    inCart = json['in_cart'];
  }
}

note

After making a debug I found that the error occurs at the getHomeData method in the Cubit File, but couldn't find any Solutions.


Solution

  • I guess the error comes from this line:

    printFullText(homeModel!.data!.banners[0].image);

    Those exclamation marks there (called "non-null assertion operators") mean that you expect homeModel not to be null and homeModel.data not to be null. That's too much to expect, because these values come from parsing an HTTP response.

    Possible solutions:

    1. Check homeModel and homeModel.data before using them, and if they are null react properly.
    2. Place everything you do with those values in a try-catch and react properly when the NoSuchMethodError is raised.
    3. Parse your JSON response using a safer system, pattern matching: https://dart.dev/language/patterns#validating-incoming-json

    In all cases, remove the ! operators.