Fix: User Model Null Safety (Friend Profiles)

Branch: fix/user-model-null-safety-friends Merged into: develop Date: March 2026

Overview

Fixed a TypeError: null: type 'Null' is not a subtype of type 'String' crash that occurred when loading friend profiles. The error appeared in user_model.g.dart inside _$UserFromJson and was the second most reported error in the app_issues table.

Root Cause

Two compounding issues:

1. Wrong parser for Supabase data

friend_service.dart used User.fromJson() to parse raw Supabase responses:

// Bad: fromJson is generated code for typed JSON (e.g., from API responses)
return (profilesResponse as List).map((json) => User.fromJson(json)).toList();

The generated _$UserFromJson code casts list items directly:

badges: (json['badges'] as List<dynamic>?)
    ?.map((e) => e as String)  // ← crashes if e is null
    .toList() ?? const [],

If the profiles table has a row where badges = [null] (or an array containing a null), e as String throws a TypeError.

2. Unsafe List.from() in fromSupabase

Even User.fromSupabase() was not fully safe:

badges: List<String>.from(profile['badges'] ?? []),
// List<String>.from([null]) → also throws TypeError

Fix

friend_service.dart

Switched to User.fromSupabase() for all Supabase profile data:

// getPendingRequests
return User.fromSupabase(profile);
 
// getFriends
return (profilesResponse as List)
    .map((json) => User.fromSupabase(json as Map<String, dynamic>))
    .toList();

user_model.dartfromSupabase

Used .whereType<String>() to filter out nulls instead of casting:

badges: (profile['badges'] as List<dynamic>?)
    ?.whereType<String>()
    .toList() ?? const [],
interests: (profile['interests'] as List<dynamic>?)
    ?.whereType<String>()
    .toList() ?? const [],

whereType<T>() silently drops any item that is not a T, including nulls. This is safer than map((e) => e as String) or List<String>.from() when the database may have inconsistent data.

Architecture Notes

User.fromJson() (generated by json_serializable) is designed for well-typed API responses where the schema is controlled and nulls inside arrays are unexpected. Supabase returns raw PostgreSQL JSON, which can have nulls in array columns if data was inserted without full validation. For Supabase data, User.fromSupabase() (handwritten) is the correct parser — it includes explicit null fallbacks for every field.

The generated .g.dart file was not modified — generated code should not be manually edited. The fix went through the source model and the calling code.