Unraveling Type Mismatch Errors in Flutter: A Guide to 'X can't be assigned to Y' and '_InternalLinkedHashMap' Issues
The Flutter news you actually need
No spam, ever. Unsubscribe in one click.
Taming the Type System: Solving Flutter’s Most Confusing Type Errors
If you’ve spent more than a few hours with Flutter, you’ve likely encountered error messages that make you scratch your head. Two of the most common—and most frustrating—are variations of “X can’t be assigned to Y” and errors involving _InternalLinkedHashMap. These messages often appear cryptic, but they’re actually the Dart type system trying to protect you from runtime disasters. Let’s unpack what’s really happening and how to fix these issues for good.
The “Same but Different” Class Problem
You create a User class, import it, and everything works—until you add another file or package. Suddenly you get:
The argument type 'User' can't be assigned to the parameter type 'User'
Wait, what? How can User not be assignable to User? This happens when you have two different classes with the same name coming from different import paths. Dart sees them as completely distinct types, even though they look identical to you.
Here’s a common scenario:
lib/models/user.dart
class User {
final String id;
final String name;
User({required this.id, required this.name});
}
lib/services/auth_service.dart
import '../models/user.dart';
class AuthService {
User? currentUser;
void updateUser(User newUser) {
currentUser = newUser;
}
}
lib/screens/profile_screen.dart (THE PROBLEM)
// Accidental duplicate in a subdirectory
import 'models/user.dart';
// Somewhere else in your imports...
import 'package:some_package/models/user.dart'; // OOPS!
void updateProfile(User user) {
// Which User class is this referring to?
// Dart can't tell either!
}
The fix is to ensure you’re importing the exact same class. Here’s how:
- Use explicit import paths: Always use absolute paths from your
libdirectory - Check for duplicate class definitions: Search your project for multiple
class Userdefinitions - Use
importaliases when necessary:
import 'package:myapp/models/user.dart' as myapp;
import 'package:external_package/models/user.dart' as external;
// Now you can be explicit about which one you mean
void processUser(myapp.User localUser, external.User externalUser) {
// No more confusion!
}
The JSON Deserialization Trap
The second common headache comes from working with JSON data:
_InternalLinkedHashMap<String, dynamic> has no instance method 'cast'
This error usually appears when you try to treat raw JSON (which becomes a Map<String, dynamic>) as if it’s already a typed object. Dart’s JSON decoding produces generic map structures, not your custom classes.
The wrong approach:
import 'dart:convert';
class Product {
final String id;
final String name;
final double price;
Product({required this.id, required this.name, required this.price});
// This will cause the error!
factory Product.fromJson(String jsonString) {
final Map<String, dynamic> data = jsonDecode(jsonString);
return Product(
id: data['id'],
name: data['name'],
price: data['price'], // Might fail if price isn't a double!
);
}
}
void main() {
final json = '{"id": "123", "name": "Widget", "price": 29.99}';
final product = Product.fromJson(json); // Works... until it doesn't
// But what about this?
final badJson = '{"id": "123", "name": "Widget", "price": "29.99"}';
final brokenProduct = Product.fromJson(badJson); // Runtime error!
}
The solution is to never trust JSON data implicitly. Here’s a safer approach:
The right approach:
class Product {
final String id;
final String name;
final double price;
Product({required this.id, required this.name, required this.price});
factory Product.fromJson(Map<String, dynamic> json) {
// Validate and convert each field
return Product(
id: json['id'] as String? ?? '',
name: json['name'] as String? ?? '',
price: (json['price'] as num?)?.toDouble() ?? 0.0,
);
}
// Helper method for parsing from string
static Product? tryParse(String jsonString) {
try {
final data = jsonDecode(jsonString) as Map<String, dynamic>;
return Product.fromJson(data);
} catch (e) {
print('Failed to parse Product: $e');
return null;
}
}
}
When Maps Aren’t What They Seem
The cast() error often appears when you chain operations incorrectly:
// Problematic code
final response = await http.get(Uri.parse('https://api.example.com/products'));
final List<Product> products = jsonDecode(response.body)['products'].cast<Product>();
// The issue: jsonDecode() returns dynamic, and you're calling cast() on
// a List<dynamic> that might not even exist!
Instead, be explicit about each step:
// Solution
final response = await http.get(Uri.parse('https://api.example.com/products'));
final Map<String, dynamic> data = jsonDecode(response.body) as Map<String, dynamic>;
final List<dynamic> productList = data['products'] as List<dynamic>;
// Now convert safely
final List<Product> products = productList
.map((item) => Product.fromJson(item as Map<String, dynamic>))
.toList();
Pro Tips for Avoiding Type Confusion
- Enable strict analysis: Add this to your
analysis_options.yaml:
analyzer:
language:
strict-casts: true
strict-inference: true
strict-raw-types: true
-
Use JSON serialization packages: For complex projects, consider
json_serializableorfreezedto generate type-safe serialization code automatically. -
Test your parsers: Always test JSON parsing with edge cases—null values, wrong types, missing fields.
-
Use type aliases for clarity:
typedef JsonMap = Map<String, dynamic>;
typedef JsonList = List<dynamic>;
Product.fromJson(JsonMap json) { ... }
Embrace the Type System
These errors might seem annoying, but they’re actually Dart’s type system working to prevent runtime crashes. By understanding what’s happening under the hood—duplicate imports, raw JSON maps, and improper type casting—you can write more robust code that fails at compile time rather than in your users’ hands.
Remember: when Dart says two types are incompatible, it’s usually right. Your job is to figure out why they’re different and align them properly. With these patterns in your toolkit, you’ll spend less time debugging and more time building great features.
This blog is produced with the assistance of AI by a human editor. Learn more
Related Posts
Flutter App Size Optimization: Strategies to Shrink Your APK/IPA Below 10MB
Flutter apps are often criticized for their larger-than-native binary sizes. This post will dive into practical, actionable strategies to significantly reduce your Flutter app's footprint, including build configurations, asset optimization, and dependency management, demonstrating how to achieve app sizes under 10MB.
Mastering In-App Subscriptions in Flutter: A Guide to Avoiding Common Pitfalls
Implementing in-app subscriptions in Flutter can be surprisingly complex, especially when dealing with cross-platform nuances, receipt validation, and managing different store configurations. This post will delve into the common challenges developers face, compare solutions like RevenueCat with custom implementations, and provide best practices for setting up and maintaining a robust subscription system.
Taming Android Emulator RAM: A Developer's Guide to Reducing Memory Usage for Flutter
Android emulators are notorious for hogging RAM, often pushing developer machines into swap and slowing down Flutter development. This post will explore the reasons behind high emulator memory consumption and provide practical, actionable strategies and tools (including a custom-built solution) to significantly reduce RAM usage, ensuring a smoother development experience without sacrificing functionality.