Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
149544 stories
·
33 followers

You Are Behind in Using AI. Here’s How to Catch Up

1 Share

If you feel behind on AI, you probably are. The tools are improving faster than most teams are changing how they work. I see this daily in my coaching of individuals and teams in industries spanning medtech, space, consumer electronics, and even AI.

The good news is that catching up doesn’t require a grand strategy, a transformation program, or perfect clarity. It requires a different posture; and a willingness to act before you feel ready.

What follows is the advice I’ve been giving leaders and senior engineers repeatedly, in real conversations, about what actually works. This advice is not just me regurgitating what I’ve read; I’ve been spending almost all of my free time (neglecting car projects) for the past 9+ months actually using the AIs “in anger” in getting Terminal.Gui to Beta. Terminal.Gui is a beast of complexity, and while it’s mostly for fun, it has serious engineering, legacy, and existing customers behind it.


1. Lead With Curiosity → Doing (Not Reading → Doing)

The teams that make progress don’t start with confidence; they start with curiosity.

Specifically: curiosity about a real problem they already own.

What I see stalling teams is not ignorance, but the desire to be right before they act. They want a complete mental model of AI, a clear strategy, and confidence they’re “doing it correctly.” That mindset guarantees delay.

The better pattern is:

  • Be curious about a concrete problem in front of you.
  • Try AI on that problem immediately.
  • Treat whatever happens as information, not success or failure.

Curiosity creates permission to experiment without certainty. Action creates the understanding you thought you needed first.

Reading and watching others can support curiosity; it cannot substitute for it. Curiosity that never turns into action is just procrastination with better branding.


2. Prototype Aggressively; Be Careful Later

Most organizations default to caution, evaluate thoroughly, compare tools, run pilots, socialize decisions. That instinct used to be reasonable. Right now, it’s counterproductive and, frankly, stupid in this age of rapid acceleration of AI capabilities.

The AIs are evolving weekly. Three weeks ago, Copilot’s CLI sucked. Now it’s surpassing Claude Code in some ways. And Claude Code has added capabilities just this week that make Copilot look dumb. Any evaluation process measured in months is obsolete before it finishes.

The guidance I keep repeating is simple:

  • Prototype fast.
  • Expect throwaway results.
  • Optimize for learning speed, not correctness.

There is almost no downside to trying something quickly on a real problem, other than spending some time. The upside is insight you cannot get any other way.

You can be careful later after you’ve learned something worth being careful about.


3. Use AI on Real Work, Not Toy Problems

You don’t learn much about AI by playing with trivial examples. You learn by pointing it at messy, production-adjacent work and seeing where it breaks, surprises you, or helps more than expected.

That means:

  • Real codebases.
  • Real constraints.
  • Real edge cases.

Even when the output isn’t production-ready, and often it won’t be, the speed at which you can explore ideas, validate approaches, or prototype alternatives changes how you think about the work.

Toy problems produce toy conclusions.


4. The Skill Has Shifted: From Prompting to Orchestrating

A year ago, the differentiating skill was “prompt engineering.” That mattered. It still does—but it’s no longer the frontier.

The emerging skill is orchestration: breaking a large problem into parts, delegating those parts to multiple AI agents, coordinating dependencies, and intervening when things go sideways.

I describe it as herding cats. Remember Tamagotchi? Orchestrating AI agents is a bit like trying to keep a bunch of Tamagotchi’s happy and healthy.

Two things are consistently true:

  • This is a very different skill than traditional software engineering.
  • You cannot do it well unless you already understand the technology deeply.

Actionable move:

  • Identify someone on your team who’s curious and motivated.
  • Make it an explicit goal for them to become excellent at managing AI agents—ideally on work you actually own, not side demos.

5. Principle‑Driven Prompting: Use Amazon PE Standards Explicitly

One of the highest‑leverage techniques I’ve seen is principle‑driven prompting.

Instead of asking AI “what should I do,” define who it should be and what standards it must uphold.

For example: prompting the AI to act as an Amazon Principal Engineer, explicitly grounding it in principles like pragmatic judgment, long‑term maintainability, empathy for operators, and raising the engineering bar.

The difference is stark:

  • With PE principles: you get targeted, realistic refactoring guidance.
  • Without them: you often get elegant but impractical greenfield rewrites.

This reveals something important: AI amplifies the values and judgment you give it. It does not supply them for free.

Here’s a prompt you might use:

“You are a Principlal Engineer who raises the bar for the Amazon Principal Engineering Community Tenets. Your role is to be a design reviewer for new engineering plans for my team. As you review and guide my engineers on their plans, you will continue to raise the bar for these tenets.”

I tweeted about this here:


6. Set Goals That Force Usage, Not Interest

“Use AI more” is not a goal; it’s a wish.

The goals that actually change behavior are usage‑forcing. Examples I’ve been recommending:

  • Use AI so much that leadership asks why token costs are spiking.
  • Designate a go‑to person for deciding which model to use when.
  • Increase, month over month, the number of issues where AI does the first pass.
  • Apply AI to processes—not just code.

If your goals don’t force different behavior, they won’t produce different results.


7. Apply AI to Processes Before Code

Writing code faster is obvious. Improving the system around the code is often higher leverage and lower risk.

Good starting points:

  • Log analysis.
  • Bug triage.
  • Updating stale documentation and code comments.
  • Reviewing designs and requirements for clarity and modularity. See the Amazon PE prompt above.

These uses build trust, save time immediately, and help teams develop intuition about where AI helps and where it doesn’t.


8. Intentionality Beats Permission

The teams making real progress aren’t waiting for an official AI strategy. They’re acting with intent.

That means:

  • Making exploration explicit, not “when you have time.”
  • Assigning ownership.
  • Setting concrete goals.
  • Sharing learnings early, even when results are imperfect.

Waiting for permission is just another way to fall further behind.


The Bottom Line

If you’re behind, acknowledge it and move. You don’t catch up by being careful, exhaustive, or perfectly informed.

You catch up by being curious, acting quickly on real work, and learning faster than your comfort level would prefer.

AI doesn’t reward hesitation. It rewards momentum.

I’m happy to chat about this with anyone curious to learn more. My office hours are free and open.

The post You Are Behind in Using AI. Here’s How to Catch Up first appeared on tig.log.
Read the whole story
alvinashcraft
39 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Level up your data security at Microsoft 365 Community Conference

1 Share

Technology is evolving at a startling rate, and it’s no longer enough for an organization to maintain their security measures, they must evolve along with it. Don’t let your data, employees, or clients become another headline. Join us in Orlando on April 21–23 for the Microsoft 365 Community Conference, where you’ll learn how to safeguard your most valuable data during expert-led discussions and hands-on sessions

.

Chart your course forward with Microsoft’s security roadmap

Do you ever wish you had a crystal ball that could show you how the changing tech landscape is going to affect your data protection needs? We can give you the next-best thing, an inside look at where the future of work is taking security solutions and what that means for you, straight from the experts leading the way.

Register now, and you’ll be in the room as Vasu Jakkal, Corporate Vice President, Microsoft Security and Rohan Kumar, Corporate Vice President, Microsoft Security unveil Microsoft’s vision for securing the new frontier of AI. They’ll discuss the ways frontier firms are protecting their data, identities, and models amid rapid AI adoption and Agents. Microsoft is helping IT and security teams adapt to AI driven work with security built in, not bolted on, gain practical guidance on how organizations are modernizing security controls, reducing data exposure, and supporting AI adoption at scale, without adding complexity for admins or end users.

And that’s not all; we have a full schedule of innovators and trailblazers scheduled throughout the program. You’ll discover exclusive insights, best practices, and strategies that will redefine your organization’s data security. Check out the speaker directory on the Microsoft 365 Community Conference homepage and then register to save your seat today.

Attend a session for every security need

We all know that securing our data is important, but it can be hard to know where to start when different sectors require different security measures. That’s why we’ve designed the sessions at Microsoft 365 Community Conference to provide attendees with both a comprehensive overview and a deep dive into specific critical topics.

• Agent 365: The control plane for all Agents with Nikita Bandyopadhyay and Sesha Mani

• Agent Lifecycle Management and Governance leveraging FastTrack with Azharullah Meer and Pratik Bhusal

• Copilot readiness & resiliency with M365 Backup & Archive with Sree Lakshmi and Aditi Gangwar

• Copilot Studio Governance for Public Sector with Richie Wallace and William McLendon

• Deep dive into Agent insights and content governance across SharePoint and Microsoft 365 with Nikita Bandyopadhyay

• Extending Microsoft Purview with APIs & SDK: Governance-by-Design for AI Apps and Agents with Martin Gagne

• How Microsoft Digital adopted Baseline Security Mode to improve Microsoft's security posture with Adriana Wood

• How Microsoft Does IT: Managing and governing Agents - empower with risk aligned oversight with David Johnson, Naveen Jangir, and Mike Powers

• How Microsoft Does IT: Microsoft 365 Governance in the age of Copilot & agents with David Johnson

• Microsoft Baseline Security Mode: Simplify, secure, succeed with Adriana Wood and Sesha Mani

• Microsoft Purview: AI-Powered Data Security for Microsoft 365 with Aashish Ramdas

• Mission Readiness - Cybersecurity and Copilot in the Public Sector with Karuana Gatimu

• Protect and govern agents with Microsoft Purview with Shilpa Ranganathan

• Protect your data from oversharing to AI with Microsoft Purview with Roberto Yglesias

• Public Sector Roadmap Review with Karuana Gatimu and Tuwanda Perez

• Securing AI with Microsoft Purview: From Visibility to Action with Shilpa Ranganathan

• Tracing the Truth: Accelerate Insider Risk Investigations with Microsoft Purview with Rod Trent

• What's new in Security & Compliance for SharePoint, OneDrive, and Teams with Sanjoyan Mustafi, Vithalprasad Gaitonde

No matter your security needs, we have a can’t-miss session waiting for you at Microsoft 365 Community Conference. Explore all Security, Compliance, and Governance sessions here.

 

You can’t halt the march of progress, but by attending the Microsoft 365 Community Conference, you can make sure you're in a position to take advantage of it. Register now, and we’ll see you April 21–23 in Orlando.

Read the whole story
alvinashcraft
40 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

App Hosting has in-console environment variables and faster builds

1 Share
Manage environment variables in the Firebase console, and learn about a small change our engineering team made to improve build times for everyone.
Read the whole story
alvinashcraft
41 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Flutter BLoC Best Practices You're Probably Missing

1 Share

Cover

Reading source code is honestly one of the best ways to understand how things actually work. I've been building Flutter apps with the BLoC pattern for years, and I thought I had it figured out, until I opened the bloc repository and started digging through the code.

What I found was code that makes you go "oh, that's why." That StateError that crashes your app at 2 AM? It's literally one if statement. The "my UI isn't updating" bug that haunts every developer? One line of code explains the whole thing.

I realized that understanding these patterns at the source level is exactly what separates solid state management from fragile code. So I decided to walk you through the BLoC source code and show how BLoC lint rules connect directly to what's actually happening under the hood.

info

You can find the full source code examples for all the rules discussed in this article in our examples repository.

Let's get started!

The Foundation

Every Bloc and Cubit extends BlocBase, which implements the core lifecycle. Let's look at the emit() method, it's where all the magic (and bugs) happen:

From bloc/lib/src/bloc_base.dart
abstract class BlocBase<State>
implements StateStreamableSource<State>, Emittable<State>, ErrorSink {

BlocBase(this._state) {
_blocObserver.onCreate(this);
}

final _stateController = StreamController<State>.broadcast();
State _state;
bool _emitted = false;


State get state => _state;


Stream<State> get stream => _stateController.stream;


bool get isClosed => _stateController.isClosed; // 👈 Delegates to stream controller




void emit(State state) {
try {
if (isClosed) {
throw StateError('Cannot emit new states after calling close');
}
if (state == _state && _emitted) return; // 👈 Same-instance check!
onChange(Change<State>(currentState: this.state, nextState: state));
_state = state;
_stateController.add(_state);
_emitted = true;
} catch (error, stackTrace) {
onError(error, stackTrace);
rethrow;
}
}



Future<void> close() async {
_blocObserver.onClose(this);
await _stateController.close(); // 👈 Closing the controller marks isClosed as true
}
}

Two lines in this code explain two of the most common BLoC bugs. Let me show you.

The isClosed Guard

if (isClosed) {
throw StateError('Cannot emit new states after calling close');
}

This is the source of "Cannot emit new states after calling close", one of the most common BLoC errors.

Here's the scenario: user opens a screen, triggers an API call, then navigates away immediately. The widget disposes, close() gets called, but the async operation is still running. When it finishes and tries to emit... crash.

This is exactly what check-is-not-closed-after-async-gap catches.

❌ Bad: Will crash if BLoC closes during the await
class SearchBlocBad extends Bloc<SearchEvent, SearchState> {
SearchBlocBad(this._repository) : super(SearchInitialImpl()) {
on<SearchQueryChanged>(_onQueryChanged);
}

final SearchRepository _repository;

Future<void> _onQueryChanged(
SearchQueryChanged event,
Emitter<SearchState> emit,
) async {
emit(SearchLoadingState());
final results = await _repository.search(event.query);
// 💥 CRASH: If user navigated away during the search, this emit throws
emit(SearchSuccessImpl(results));
}
}

The fix is simple, just check isClosed before emitting:

✅ Good: Always check isClosed after async gaps
  Future<void> _onQueryChanged(
SearchQueryChanged event,
Emitter<SearchState> emit,
) async {
emit(SearchLoadingState());
final results = await _repository.search(event.query);
// ✅ Safe: Check before emitting
if (!isClosed) {
emit(SearchSuccessImpl(results));
}
}

The rule also supports custom event, listening methods through configuration:

analysis_options.yaml
dcm:
rules:
- check-is-not-closed-after-async-gap:
additional-methods:
- customOn

This single rule eliminates an entire category of production crashes. I can't stress enough how important it is.

The Equality Short-Circuit

Now look at the second critical line:

if (state == _state && _emitted) return;

This line silently skips emits when the state hasn't changed. The == operator uses your state's equality implementation. If you emit the same instance, identical(this, other) returns true and the UI never rebuilds.

This is the infamous "my UI isn't updating" bug, and it's what emit-new-bloc-state-instances catches.

❌ Bad: Emitting the same instance, UI won't update
class UserBloc extends Bloc<UserEvent, UserState> {
UserBloc() : super(UserState()) {
on<UpdateNameEvent>((event, emit) {
// 💥 This modifies the existing state object
state.name = event.name;

// 💥 Same instance, same reference, UI ignores this emit!
emit(state);
});
}
}

The solution is to always create a new state instance:

✅ Good: Always emit a new instance
class UserBloc extends Bloc<UserEvent, UserState> {
UserBloc() : super(const UserState()) {
on<UpdateNameEvent>((event, emit) {
// ✅ New instance with updated values
emit(state.copyWith(name: event.name));
});
}
}


class UserState {
final String name;

const UserState({this.name = ''});

UserState copyWith({String? name}) {
return UserState(name: name ?? this.name);
}
}

This bug is a nightmare to debug because everything looks right. The event fires, the handler runs, emit gets called... but nothing happens on screen. The rule catches it instantly.

Read more about BLoC Library FAQ: State not updating

The Provider Lifecycle

The BlocProvider source code shows exactly why two common memory bugs happen:

From flutter_bloc/lib/src/bloc_provider.dart
class BlocProvider<T extends StateStreamableSource<Object?>>
extends SingleChildStatelessWidget {

// Constructor for create: mode
const BlocProvider({
required T Function(BuildContext context) create,
Key? key,
this.child,
this.lazy = true,
}) : _create = create,
_value = null,
super(key: key, child: child);

// Named constructor for .value mode
const BlocProvider.value({
required T value,
Key? key,
this.child,
}) : _value = value,
_create = null,
lazy = true,
super(key: key, child: child);

final Widget? child;
final bool lazy;
final T Function(BuildContext context)? _create;
final T? _value;


Widget buildWithChild(BuildContext context, Widget? child) {
assert(
child != null,
'$runtimeType used outside of MultiBlocProvider must specify a child',
);
final value = _value;
return value != null
? InheritedProvider<T>.value(
value: value,
startListening: _startListening,
lazy: lazy,
// ❌ NO dispose callback for .value!
child: child,
)
: InheritedProvider<T>(
create: _create,
dispose: (_, bloc) => bloc.close(), // ✅ Auto-close for create:
startListening: _startListening,
lazy: lazy,
child: child,
);
}
}

Look at the two code paths:

  • create: mode: Passes dispose: (_, bloc) => bloc.close() to the InheritedProvider
  • .value mode: No dispose callback at all

This is exactly why the provider lifecycle rules exist.

BLoC Provider LifecycleBLoC Provider Lifecycle

Using the Wrong Provider Type

Before we even get to create: vs .value, there's an even more fundamental mistake:

| Using a generic Provider when you should be using BlocProvider.

If your project uses both bloc and provider packages, it's easy to accidentally wrap a BLoC in a plain Provider, which bypasses the lifecycle management that BlocProvider provides.

This is what prefer-correct-bloc-provider catches.

❌ Bad: Using Provider instead of BlocProvider
// 💥 This won't auto-close the BLoC when disposed!
final provider = Provider<CounterBloc>(
create: (context) => CounterBloc(),
child: const CounterPage(),
);
✅ Good: Use BlocProvider for BLoCs
final blocProvider = BlocProvider<CounterBloc>(
create: (context) => CounterBloc(),
child: const CounterPage(),
);

Passing Existing Instances to create:

BlocProvider(create: ...) takes ownership of the BLoC instance. It will call close() when the provider is disposed. But if you pass an existing instance to create:, you've created a lifecycle mismatch.

This is what avoid-existing-instances-in-bloc-provider catches.

❌ Bad: BlocProvider will close an instance it doesn't own
class MyApp extends StatelessWidget {
// 🚨 This BLoC is created outside the provider
final counterBloc = CounterBloc();


Widget build(BuildContext context) {
return BlocProvider(
// 💥 BlocProvider will close this when disposed,
// but you might try to use it elsewhere!
create: (_) => counterBloc,
child: const CounterPage(),
);
}
}

Instead, let the provider create and own the instance:

✅ Good: Let BlocProvider create and own the instance
class MyApp extends StatelessWidget {

Widget build(BuildContext context) {
return BlocProvider(
// ✅ Provider creates it, provider owns it, provider closes it
create: (_) => CounterBloc(),
child: const CounterPage(),
);
}
}

This causes double-close errors or worse use-after-close bugs that are super hard to track down. The BLoC seems fine sometimes and crashes randomly other times.

Instantiating in .value

This is the flip side. When you use .value, you're telling the provider: "I'm giving you an existing instance; don't touch its lifecycle." But if you instantiate a new BLoC directly in .value, nobody will close it.

This is what avoid-instantiating-in-bloc-value-provider catches.

❌ Bad: Memory leak, this BLoC will never be closed
BlocProvider.value(
// 💥 MEMORY LEAK: Who closes this? Nobody!
value: CounterBloc(),
child: const CounterPage(),
);

When using .value, you must manage the lifecycle yourself:

✅ Good: Pass an existing instance that you manage
class ParentWidget extends StatefulWidget {

State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
late final CounterBloc _counterBloc;


void initState() {
super.initState();
_counterBloc = CounterBloc();
}


void dispose() {
_counterBloc.close(); // ✅ You manage the lifecycle
super.dispose();
}


Widget build(BuildContext context) {
return BlocProvider.value(
value: _counterBloc, // ✅ Existing instance
child: const CounterPage(),
);
}
}

Silent memory leaks accumulate until your app becomes sluggish and eventually crashes. These leaks don't show up in testing but destroy user experience in production.

info

We also have written a comprehensive blog post about memory leaks and how it happens in Flutter in general. If you are interested to deep dive, read Let's talk about memory leaks in Dart and Flutter.

The Event System

The Bloc class builds on BlocBase by adding event-driven architecture. Let's look at how on<E>() works:

From bloc/lib/src/bloc.dart
abstract class Bloc<Event, State> extends BlocBase<State>
implements BlocEventSink<Event> {

Bloc(super.initialState);

final _eventController = StreamController<Event>.broadcast();
final _subscriptions = <StreamSubscription<dynamic>>[];
final _handlers = <_Handler>[];
final _emitters = <_Emitter<dynamic>>[];


void add(Event event) {
assert(() {
final handlerExists = _handlers.any((handler) => handler.isType(event));
if (!handlerExists) {
final eventType = event.runtimeType;
throw StateError(
'''add($eventType) was called without a registered event handler.\n'''
'''Make sure to register a handler via on<$eventType>((event, emit) {...})''',
);
}
return true;
}());

try {
onEvent(event);
_eventController.add(event);
} catch (error, stackTrace) {
onError(error, stackTrace);
rethrow;
}
}

void on<E extends Event>(
EventHandler<E, State> handler, {
EventTransformer<E>? transformer,
}) {
// Prevent duplicate handlers for the same event type
assert(() {
final handlerExists = _handlers.any((handler) => handler.type == E);
if (handlerExists) {
throw StateError(
'on<$E> was called multiple times. '
'There should only be a single event handler per event type.',
);
}
_handlers.add(_Handler(isType: (dynamic e) => e is E, type: E));
return true;
}());

final subscription = (transformer ?? _eventTransformer)(
_eventController.stream.where((event) => event is E).cast<E>(),
(dynamic event) {
void onEmit(State state) {
if (isClosed) return;
if (this.state == state && _emitted) return;
onTransition(Transition(
currentState: this.state,
event: event as E,
nextState: state,
));
emit(state);
}
// ... handler invocation with Emitter
},
).listen(null);

_subscriptions.add(subscription);
}
}

Key takeaways from this code:

  • Events flow through a StreamController: The add() method doesn't directly call handlers, it pushes to a stream that handlers subscribe to. This is the unidirectional flow that public methods violate.

  • onTransition() captures the full picture: Unlike onChange() (which only has before/after state), transitions include the triggering event. This is why BlocObserver can provide complete tracing if you use events.

  • Guard clauses everywhere: Notice if (isClosed) return; appears multiple times. The library tries to protect you, but async gaps can still slip through.

The Change and Transition Types

Understanding these immutable records explains why we need immutable events and states:

From bloc/lib/src/change.dart and transition.dart

class Change<State> {
const Change({required this.currentState, required this.nextState});

final State currentState;
final State nextState;


bool operator ==(Object other) =>
identical(this, other) ||
other is Change<State> &&
runtimeType == other.runtimeType &&
currentState == other.currentState &&
nextState == other.nextState;
}


class Transition<Event, State> extends Change<State> {
const Transition({
required State currentState,
required this.event,
required State nextState,
}) : super(currentState: currentState, nextState: nextState);

final Event event; // 👈 The event is captured in the transition
}

If your events are mutable, a Transition captured by BlocObserver could have its event changed after being logged, creating weird bugs that are almost impossible to track down.

Public Methods Bypass Events

The entire point of the BLoC pattern is unidirectional data flow. Events go in, states come out. When you add public methods to a BLoC, you create a back door that bypasses the event system.

This is what avoid-bloc-public-methods catches.

❌ Bad: Public methods bypass the event system
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);

// 🚨 This bypasses events entirely!
// BlocObserver won't see this change.
// You can't replay this action.
// State history becomes incomplete.
void incrementDirectly() {
emit(state + 1);
}


void onChange(Change<int> change) {
super.onChange(change);
print(change); // Won't log incrementDirectly() calls properly
}
}

Keep all state mutations flowing through events:

✅ Good: Everything flows through events
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<IncrementPressed>((event, emit) => emit(state + 1));
}

// Keep implementation details private
void _logAnalytics() { /* ... */ }


void onChange(Change<int> change) {
super.onChange(change);
print(change); // ✅ All state changes are tracked
}
}

// Usage in widget:
context.read<CounterBloc>().add(IncrementPressed());

When you use events, Bloc.on<E>() creates Transition objects that include the event. But Cubit.emit() (and direct emit() calls from public methods) only create Change objects, no event information. This is why BlocObserver.onTransition() gives you full traceability, but only if you actually use events.

Every public method is technical debt. Your future self (or teammates) will have to figure out which state changes come from events and which come from direct method calls.

Context and BLoC-to-BLoC

Two more architectural rules deserve attention.

BuildContext Doesn't Belong in BLoCs

When you pass BuildContext to a BLoC event or Cubit method, you're creating a vulnerable situation. The context is tied to a specific widget in the tree. If that widget is disposed while the BLoC is still processing, you get errors like "looking up a deactivated widget's ancestor."

Beyond crashes, passing context destroys testability. How do you unit test a BLoC that needs a real BuildContext?

This is what avoid-passing-build-context-to-blocs catches.

❌ Bad: Context coupling creates crashes and untestable code
// In your widget:
bloc.add(LoadUserEvent(context)); // 🚨 Passing context to event

// In your event:
class LoadUserEvent extends UserEvent {
final BuildContext context; // 💥 This will cause problems
LoadUserEvent(this.context);
}

// In your Cubit:
class SettingsCubit extends Cubit<SettingsState> {
// 💥 What happens when this context is no longer mounted?
void loadTheme(BuildContext context) async {
final theme = Theme.of(context);
// ... use theme
}
}

Extract the data you need before passing it to the BLoC:

✅ Good: Pass data, not context
// In your widget:
final userId = context.read<AuthBloc>().state.userId;
bloc.add(LoadUserEvent(userId)); // ✅ Pass the data you need

// In your event:
class LoadUserEvent extends UserEvent {
final String userId; // ✅ Just the data
LoadUserEvent(this.userId);
}

// In your Cubit:
class SettingsCubit extends Cubit<SettingsState> {
final ThemeRepository _themeRepository;

SettingsCubit(this._themeRepository) : super(SettingsInitial());

void loadTheme() async {
// ✅ Get theme data from repository, not context
final theme = await _themeRepository.getCurrentTheme();
emit(SettingsLoaded(theme));
}
}

BLoCs Should Not Know About Each Other

It's tempting to inject one BLoC into another when they need to coordinate. But this creates tight coupling and circular dependency risks. If BlocA depends on BlocB, and BlocB needs data from BlocA, you've got a maintenance nightmare.

This is what avoid-passing-bloc-to-bloc catches.

❌ Bad: Direct BLoC-to-BLoC coupling
class CartBloc extends Bloc<CartEvent, CartState> {
final AuthBloc authBloc; // 🚨 Direct dependency
late final StreamSubscription _authSubscription;

CartBloc(this.authBloc) : super(CartInitial()) {
// 🚨 Tight coupling: CartBloc knows too much about AuthBloc
_authSubscription = authBloc.stream.listen((authState) {
if (authState is Unauthenticated) {
add(ClearCartEvent());
}
});
}


Future<void> close() {
_authSubscription.cancel();
return super.close();
}
}

There are two better approaches: coordinate in the widget layer, or use a shared stream:

✅ Good: Coordinate at the presentation layer or through repositories
// Option 1: Coordinate in the widget layer
class CartPage extends StatelessWidget {

Widget build(BuildContext context) {
return BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
if (state is Unauthenticated) {
// ✅ Widget coordinates between BLoCs
context.read<CartBloc>().add(ClearCartEvent());
}
},
child: const CartView(),
);
}
}

// Option 2: Use a shared repository/stream
class CartBloc extends Bloc<CartEvent, CartState> {
final CartRepository _cartRepository;
final Stream<AuthStatus> _authStatusStream; // ✅ Stream, not BLoC

CartBloc({
required CartRepository cartRepository,
required Stream<AuthStatus> authStatusStream,
}) : _cartRepository = cartRepository,
_authStatusStream = authStatusStream,
super(CartInitial()) {
_authStatusStream.listen((status) {
if (status == AuthStatus.unauthenticated) {
add(ClearCartEvent());
}
});
}
}
BLoC Coordination PatternsBLoC Coordination Patterns

The "quick fix" of passing one BLoC to another always leads to maintenance headaches as the app grows.

The BlocBuilder Rebuild Logic

Let's look at how BlocBuilder decides when to rebuild:

From flutter_bloc/lib/src/bloc_builder.dart
typedef BlocBuilderCondition<S> = bool Function(S previous, S current);

class BlocBuilder<B extends StateStreamable<S>, S>
extends BlocBuilderBase<B, S> {

const BlocBuilder({
required this.builder,
Key? key,
B? bloc,
BlocBuilderCondition<S>? buildWhen,
}) : super(key: key, bloc: bloc, buildWhen: buildWhen);

final BlocWidgetBuilder<S> builder;


Widget build(BuildContext context, S state) => builder(context, state);
}

class _BlocBuilderBaseState<B extends StateStreamable<S>, S>
extends State<BlocBuilderBase<B, S>> {

late B _bloc;
late S _state;


void initState() {
super.initState();
_bloc = widget.bloc ?? context.read<B>();
_state = _bloc.state;
}


Widget build(BuildContext context) {
if (widget.bloc == null) {
// Trigger a rebuild if the bloc reference has changed.
context.select<B, bool>((bloc) => identical(_bloc, bloc));
}
return BlocListener<B, S>(
bloc: _bloc,
listenWhen: widget.buildWhen,
listener: (context, state) => setState(() => _state = state),
child: widget.build(context, _state),
);
}
}

The key insight here: BlocBuilder delegates to BlocListener under the hood! The buildWhen callback is passed as listenWhen to the listener. When a new state arrives:

  1. BlocListener checks if listenWhen (your buildWhen) returns true
  2. If true, the listener calls setState(() => _state = state), triggering a rebuild
  3. If false, the listener doesn't fire, so no setState, no rebuild

This is a powerful optimization, but if you leave buildWhen empty or forget to add it for expensive widgets, you're missing optimization opportunities.

This is what avoid-empty-build-when catches.

❌ Bad: BlocBuilder without buildWhen might rebuild too often
BlocBuilder<CounterBloc, int>(
// 🚨 No buildWhen means this rebuilds on EVERY state change
// If the builder is expensive, this could cause jank
builder: (context, state) {
return ExpensiveWidget(count: state);
},
);

Add an explicit buildWhen to control when rebuilds happen:

✅ Good: Explicit buildWhen for optimization
BlocBuilder<CounterBloc, int>(
buildWhen: (previous, current) {
// ✅ Only rebuild when the count actually changes
return previous != current;
},
builder: (context, state) {
return ExpensiveWidget(count: state);
},
);

Dart 3 Type Safety

Dart 3 introduced sealed classes, and they're a game-changer for BLoC patterns. These rules leverage Dart 3's type system to catch bugs at compile time rather than runtime.

Sealed Events and States

Dart 3's sealed classes enable exhaustiveness checking. If your events and states are sealed, the compiler will warn you when you add a new event but forget to handle it, or when your switch statement doesn't cover all states.

This is what prefer-sealed-bloc-events and prefer-sealed-bloc-state enforce.

❌ Bad: No compile-time exhaustiveness checking
// Without sealed, nothing stops you from forgetting a case
abstract class CounterEvent {}

class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
class ResetEvent extends CounterEvent {} // Added later...

// In a widget somewhere:
void handleEvent(CounterEvent event) {
if (event is IncrementEvent) {
// handle
} else if (event is DecrementEvent) {
// handle
}
// 💥 Oops! ResetEvent is silently ignored. No warning.
}

With sealed classes, the compiler enforces exhaustive handling:

✅ Good: Sealed classes enable exhaustiveness checking
sealed class CounterEvent {}

final class IncrementEvent extends CounterEvent {}
final class DecrementEvent extends CounterEvent {}
final class ResetEvent extends CounterEvent {}

// Now this is a compile error:
void handleEvent(CounterEvent event) {
switch (event) {
case IncrementEvent():
// handle
case DecrementEvent():
// handle
// ❌ Compile error: The type 'CounterEvent' is not exhaustively matched
// by the switch cases since it doesn't match 'ResetEvent()'.
}
}

You can configure the naming pattern:

analysis_options.yaml
dcm:
rules:
- prefer-sealed-bloc-events:
name-pattern: Event$
- prefer-sealed-bloc-state:
name-pattern: State$

Immutable Events

If an event can be modified after it's created, you risk mutation-based side effects. An event handler might change a property, affecting how subsequent handlers (or replays) process the same event.

This is what prefer-immutable-bloc-events catches.

❌ Bad: Mutable events can be modified after dispatch
class UpdateUserEvent extends UserEvent {
String name; // 🚨 Mutable field

UpdateUserEvent(this.name);
}

// Somewhere in your code:
final event = UpdateUserEvent('Alice');
bloc.add(event);

// Later, accidentally or intentionally:
event.name = 'Bob'; // 💥 Mutated after dispatch!
// If event handlers cache or compare events, this causes chaos.

Make events immutable with final fields and const constructors:

✅ Good: Immutable events are predictable

sealed class UserEvent {}


final class UpdateUserEvent extends UserEvent {
final String name; // ✅ Final field

const UpdateUserEvent(this.name);
}

Design Patterns in BLoC

When you look at the BLoC source code, you can spot several design patterns at work:

PatternWhere It AppearsPurpose
ObserverBlocObserver, stream.listen()React to state changes without tight coupling
CommandEvent classesEncapsulate actions as objects for replay, logging
MediatorBlocProviderDecouple BLoC creation from widget tree
StrategyEventTransformerSwap event processing strategies (debounce, throttle)
Immutable Value ObjectChange, Transition, StatesEnsure predictable, traceable state history

Understanding these patterns explains why the BLoC architecture works and why breaking its contracts (public methods, mutable events, coupled BLoCs) causes problems.

Code Style Rules

These rules don't prevent bugs, but they make your codebase more consistent and easier to maintain.

prefer-multi-bloc-provider

When you need multiple BLoCs at the same level, nesting BlocProvider widgets creates an indentation nightmare. MultiBlocProvider is syntactic sugar that keeps things flat.

❌ Bad: Nesting hell
BlocProvider<AuthBloc>(
create: (context) => AuthBloc(),
child: BlocProvider<SettingsBloc>(
create: (context) => SettingsBloc(),
child: BlocProvider<ThemeBloc>(
create: (context) => ThemeBloc(),
child: BlocProvider<AnalyticsBloc>(
create: (context) => AnalyticsBloc(),
child: const MyApp(), // 4 levels deep!
),
),
),
);

Use MultiBlocProvider to keep things flat:

✅ Good: Flat and readable
MultiBlocProvider(
providers: [
BlocProvider<AuthBloc>(create: (context) => AuthBloc()),
BlocProvider<SettingsBloc>(create: (context) => SettingsBloc()),
BlocProvider<ThemeBloc>(create: (context) => ThemeBloc()),
BlocProvider<AnalyticsBloc>(create: (context) => AnalyticsBloc()),
],
child: const MyApp(),
);

prefer-bloc-extensions

Use context.read<T>() instead of BlocProvider.of<T>(context). The extension methods are shorter, more consistent, and make it harder to forget listen: true when you need watch semantics.

❌ Bad: Verbose and easy to forget listen parameter
// Have to remember listen: false is default (read behavior)
final bloc = BlocProvider.of<CounterBloc>(context);

// Easy to forget listen: true when you need reactivity
final bloc = BlocProvider.of<CounterBloc>(context, listen: true);

The extension methods are clearer and more concise:

✅ Good: Clear intent, shorter code
// Read once (doesn't rebuild on state changes)
final bloc = context.read<CounterBloc>();

// Watch (rebuilds when state changes)
final bloc = context.watch<CounterBloc>();

prefer-bloc-event-suffix & prefer-bloc-state-suffix

When events are named FetchUsers instead of FetchUsersEvent, it becomes harder to distinguish events from methods, classes, or other constructs at a glance.

❌ Bad: Inconsistent naming
// Is this a class? A method? An event?
class FetchUsers {}
class UpdateProfile {}
class Loading {}

Consistent suffixes make the intent immediately clear:

✅ Good: Clear suffixes
// Immediately clear what these are
class FetchUsersEvent {}
class UpdateProfileEvent {}
class LoadingState {}

You can customize the pattern:

analysis_options.yaml
dcm:
rules:
- prefer-bloc-event-suffix:
name-pattern: Event$
ignore-subclasses: true
- prefer-bloc-state-suffix:
name-pattern: State$
ignore-subclasses: true

Here's a starting analysis_options.yaml configuration that enables all BLoC rules with sensible defaults:

analysis_options.yaml
dcm:
rules:
# Crash & leak prevention
- check-is-not-closed-after-async-gap
- avoid-existing-instances-in-bloc-provider
- avoid-instantiating-in-bloc-value-provider
- avoid-passing-build-context-to-blocs

# Architectural integrity
- avoid-bloc-public-methods
- avoid-bloc-public-fields
- emit-new-bloc-state-instances
- avoid-passing-bloc-to-bloc

# Modern type safety (Dart 3+)
- prefer-sealed-bloc-events:
name-pattern: Event$
- prefer-sealed-bloc-state:
name-pattern: State$
- prefer-immutable-bloc-events:
name-pattern: Event$
- prefer-immutable-bloc-state:
name-pattern: State$

# Code style
- prefer-multi-bloc-provider
- prefer-bloc-extensions
- avoid-empty-build-when
- prefer-bloc-event-suffix:
name-pattern: Event$
ignore-subclasses: true
- prefer-bloc-state-suffix:
name-pattern: State$
ignore-subclasses: true

Conclusion

Reading source code is one of the best ways to level up as a developer. The next time you encounter a bug or wonder why a pattern exists, open the source and look for yourself. You might be surprised what you find.

Happy coding!

Enjoying this article?

Subscribe to get our latest articles and product updates by email.

Read the whole story
alvinashcraft
41 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Deploying Python apps on Azure App Service

1 Share
From: Microsoft Azure Developers
Duration: 11:15
Views: 24

In this video, Scott and Nir will go through the simplest way to get started with Python in the cloud. The video explores different deployment approaches and recent changes to the platform to be even simpler.

🌮 Chapter Markers:
00:00 - 00:12 – Introduction by Scott
00:12 – 08:12 - Deploying to App Service with uv
08:12 - 09:47 - Container Terminal Access
09:47 - 11:11 - Summary and where to learn more

🌮 Resources
Learn Docs: https://learn.microsoft.com/en-us/azure/app-service/overview
Azure Product page: https://azure.microsoft.com/en-us/products/app-service/


🌮 Follow us on social:
Scott Hanselman | @SHanselman – https://x.com/SHanselman
Azure Friday | @AzureFriday – https://x.com/AzureFriday

Blog - https://aka.ms/azuredevelopers/blog
Twitter - https://aka.ms/azuredevelopers/twitter
LinkedIn - https://aka.ms/azuredevelopers/linkedin
Twitch - https://aka.ms/azuredevelopers/twitch

#azuredeveloper #azure

Read the whole story
alvinashcraft
41 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

What Vibe Coding is Turning Into

1 Share
From: AIDailyBrief
Duration: 10:41
Views: 1,476

Perplexity Computer (Enterprise and Personal) and Replit Agent Four showcase a shift toward persistent, multi-agent workflows and extensible vibecoding canvases. Major themes include multi-model orchestration, always-on local context via Mac Mini integration, parallel task execution, multiplayer collaboration, and usage-based enterprise pricing. Key implications are agent-centric IDEs, intensified security debates over system access, and faster company pivots as AI expands into broader knowledge work.

The AI Daily Brief helps you understand the most important news and discussions in AI.
Subscribe to the podcast version of The AI Daily Brief wherever you listen: https://pod.link/1680633614
Get it ad free at http://patreon.com/aidailybrief
Learn more about the show https://aidailybrief.ai/

Read the whole story
alvinashcraft
41 minutes ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories