Flutter’s didChangeDependencies
lifecycle method is a common source of redundant network requests and data reloads. Based on real-world code examples from a notes app, let’s explore why this happens and how to fix it.
When Does didChangeDependencies
Trigger?
didChangeDependencies
in Flutter triggers in these scenarios:
- Immediately after initState()
- When an InheritedWidget ancestor changes
- When the widget's dependencies change
In the provided HomePage
code:
@override
void didChangeDependencies() {
super.didChangeDependencies();
navigateToPage(currentPageNumber); // ❌ Unconditional call
}
This causes duplicate API calls every time the event being triggerd.
The Fix: Initialize-Once Flag
class HomePageState extends State<HomePage> {
...
bool _isInitialized = false; // Add flag
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (!_isInitialized) {
_isInitialized = true;
navigateToPage(currentPageNumber); // ✅ Only first load
}
}
...
}
Why This Works
- First Load: Initializes data once
- Subsequent Route Changes: Skips reload unless explicitly refreshed
- Memory Efficiency: Prevents duplicate API calls (evident from the
NotesService
cache-less implementation)
Key Takeaways
didChangeDependencies
isn’t just for initialization
- Always guard data-loading logic with flags
... skipping 1000 words about bad solutions ...
The Clean/Best Solution
Here's a pattern that elegantly handles this situation:
public void RefreshGrid()
{
// 1. Store the current entity before refresh
SomeEntity currentEntity = null;
if (dataGrid.ActiveRow != null)
{
currentEntity = dataGrid.ActiveRow.ListObject as SomeEntity;
}
// 2. Refresh the grid
dataGrid.RefreshData();
// 3. Restore selection using entity ID
if (currentEntity != null)
{
dataGrid.ActiveRow = dataGrid.Rows.FirstOrDefault(r =>
((SomeEntity)r.ListObject).Id == currentEntity.Id);
}
}
Why This Pattern Is Best Practice
- Type Safety: Using the strongly-typed entity object instead of raw values
- Identity-Based: Uses unique IDs instead of volatile row positions
- Null-Safe: Handles cases where no row is selected
- Concise: LINQ makes the code readable and maintainable
- Reliable: Works even if data order changes or rows are filtered
Wen working with QuickFix/n ibrary , efficient logging is crucial for troubleshooting. Here's how to implement a custom logging solution that routes QuickFix logs through NLog to your ELK stack.
Key Components
- NLogAdapter: A custom adapter that implements QuickFix's
ILog
interface:
public class NLogAdapter(ILogger logger) : ILog
{
private const string HeartbeatPattern = @"\x0135=0\x01";
private static readonly Regex HeartbeatRegex = new(HeartbeatPattern, RegexOptions.Compiled);
private static bool IsHeartBeat(string message) => HeartbeatRegex.IsMatch(message);
public void OnIncoming(string message)
{
if (!IsHeartBeat(message))
{
logger.Info("Incoming: {Message}", message);
}
}
// ... other implementations
}
- NLogQuickFixLogFactory: A factory class to create log instances:
public class NLogQuickFixLogFactory(ILog logger) : ILogFactory
{
public ILog Create(SessionID sessionId) => logger;
public ILog CreateNonSessionLog() => logger;
}
Implementation Steps
- Register Dependencies in your DI container:
builder.Services.AddSingleton<NLog.ILogger>(_ => LogManager.GetCurrentClassLogger());
builder.Services.AddSingleton<ILog, NLogAdapter>();
builder.Services.AddSingleton<ILogFactory, NLogQuickFixLogFactory>();
- Configure QuickFix to use the custom logger:
var initiator = new SocketInitiator(
clientApp,
storeFactory,
sessionSettings,
new NLogQuickFixLogFactory(quickfixLogger) // Use custom logger injeted by ILog here
);
Key Features
- Heartbeat Filtering: Reduces log noise by filtering out FIX heartbeat messages
- Structured Logging: Uses NLog's structured logging format for better parsing in ELK
- Separation of Concerns: Cleanly separates QuickFix logging from application logging
Benefits
- Centralized logging in ELK stack
- Better debugging apabilities
- Reduced log volume through heartbeat filtering
- Consistent logging format across your application
Here’s a consolidated list of common NUnit Assert
statements, categorized by their purpose. This should cover most of the common scenarios:
Basic Assertions
-
Equality:
Assert.That(actual, Is.EqualTo(expected));
Assert.That(actual, Is.Not.EqualTo(expected));
-
Boolean Conditions:
Assert.That(condition, Is.True);
Assert.That(condition, Is.False);
-
Null Checks:
Assert.That(obj, Is.Null);
Assert.That(obj, Is.Not.Null);
String Assertions
-
Contains:
Assert.That(actualString, Does.Contain(substring));
-
Starts With / Ends With:
Assert.That(actualString, Does.StartWith(prefix));
Assert.That(actualString, Does.EndWith(suffix));
-
Empty or Not Empty:
Assert.That(actualString, Is.Empty);
Assert.That(actualString, Is.Not.Empty);
-
Matches Regex:
Assert.That(actualString, Does.Match(regexPattern));
Collection Assertions
-
Contains Item:
Assert.That(collection, Does.Contain(item));
-
Has Specific Count:
Assert.That(collection, Has.Count.EqualTo(expectedCount));
-
Empty or Not Empty:
Assert.That(collection, Is.Empty);
Assert.That(collection, Is.Not.Empty);
-
Unique Items:
Assert.That(collection, Is.Unique);
Numeric Assertions
-
Greater Than / Less Than:
Assert.That(actual, Is.GreaterThan(expected));
Assert.That(actual, Is.LessThan(expected));
-
Greater Than or Equal / Less Than or Equal:
Assert.That(actual, Is.GreaterThanOrEqualTo(expected));
Assert.That(actual, Is.LessThanOrEqualTo(expected));
-
In Range:
Assert.That(actual, Is.InRange(lower, upper));
Type Assertions
-
Instance of Type:
Assert.That(obj, Is.TypeOf<ExpectedType>());
Assert.That(obj, Is.InstanceOf<ExpectedType>());
-
Assignable From:
Assert.That(obj, Is.AssignableTo<ExpectedType>());
Exception Assertions
-
Throws Exception:
Assert.Throws<ExpectedExceptionType>(() => { methodCall(); });
-
Throws Specific Exception with Condition:
var ex = Assert.Throws<ExpectedExceptionType>(() => { methodCall(); });
Assert.That(ex.Message, Does.Contain("expected message"));
Miscellaneous
-
Same Instance:
Assert.That(actual, Is.SameAs(expected));
Assert.That(actual, Is.Not.SameAs(expected));
-
Applies a Condition:
Assert.That(collection, Has.Some.Matches<ExpectedType>(item => item.Condition));
-
Delayed Constraints (Asynchronous):
Assert.That(() => condition, Is.True.After(500).PollEvery(50));
-
Group related assertions together to improve readability and reporting:
Assert.Multiple(() =>
{
Assert.That(okResult, Is.Not.Null, "okResult should not be null");
Assert.That(okResult.Value, Is.TypeOf<string>(), "Value should be of type string");
});
The choice between = null!
and = string.Empty
depends on the context and the intent of the code. Here's a breakdown to help you decide:
= null!
When to Use:
-
You Intend to Initialize the Field Later:
-
Use = null!
when the property or field is expected to be set by a constructor, a method, or dependency injection after the object is created.
-
Example:
public string Name { get; set; } = null!;
-
Why: null!
tells the compiler to trust you that the value will be assigned before being accessed.
-
Avoid Premature Default Values:
- If a default value like
string.Empty
doesn't make sense for the context, null!
keeps the intent clearer.
-
For Models or Data Contracts:
-
In cases where fields are expected to match a schema or be deserialized from external sources, it might be better to indicate "unknown until set."
-
Example:
public string Username { get; set; } = null!; // Will be deserialized later
= string.Empty
When to Use:
-
Default Values Are Meaningful:
-
Use = string.Empty
when an empty string is a valid and reasonable default.
-
Example:
public string Name { get; set; } = string.Empty;
-
Why: This avoids null-checking or potential runtime errors when accessing the property.
-
Simpler to Work With:
- If the field/property is commonly accessed and null is not a meaningful value in your application, an empty string simplifies the logic (avoids extra null-checks).
-
UI or Display-Friendly Fields:
-
For fields used in UI contexts, empty strings often make more sense than null
:
public string Description { get; set; } = string.Empty;
Key Differences
Aspect |
= null! |
= string.Empty |
Purpose |
Indicates value will be set later. |
Provides an immediate, valid default. |
Compiler Warnings |
Suppresses nullability warnings (unsafe). |
Avoids nullability warnings entirely. |
Clarity |
Explicitly states "not initialized yet". |
Implies "initialized to empty value now". |
Best Fit |
Models, external contracts, DI patterns. |
Readily usable properties or fields. |
When to Avoid = null!
- When it's unclear who or what will initialize the property.
- When using
null
might lead to accidental runtime errors.
- When the property will be frequently accessed before initialization.
Recommendation
- Use
= string.Empty
when empty strings make sense as defaults and simplify code.
- Use
= null!
when initialization will occur later, and null isn't a valid or meaningful runtime value.
You know who is the real author of this article, don't you? :P