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

Migrating Agentic Code Python -> C# Part 3

1 Share

In the previous blog post (Part 2) we began the migration by setting up the configuration. In this post, we’ll tackle the Blogger, which acts as an orchestrator for the agents.

In the python version of our program the blogger_prompt_template is in its own cell and fairly short. In the C# version we create a file Prompts.cs. The class is static and has a const string for each prompt. Let’s start with the Blogger prompt:

namespace BlogMigration;

public static class Prompts
{
    public const string BloggerPromptTemplate = """
You are a blogger managing a blog post creation workflow.

Current Task: {main_task}

Current State:
- Research Findings: {research_findings}
- Blog Draft: {draft}
- Reviewer Feedback: {review_notes}
- Revision Number: {revision_number}

Your goal is to ensure a clear, engaging, and valuable blog post targeted at software developers.

Decide the next step and respond only with a JSON object (no extra text):
{
    "next_step": "researcher" or "author" or "END",
  "task_description": "Brief description of what needs to be done next"
}

Decision Rules:
- If no research exists, choose "researcher"
- If research exists but no draft, choose "author"
- If draft exists and reviewer says "APPROVED", choose "END"
- If draft needs revision, choose "author"
- If revision_number >= 4, choose "END"
""";

The triple quotes in a C# string create a raw string literal, introduced in C# 11. With this no escaping is needed and the string can be multi-line. There’s more to it, and I’ll refer you to the C# documentation.

With that, we’re ready to create the BloggerChain. The structure matches the Python code closely, of course using C# syntax.

using System.Text.Json;
using Microsoft.Extensions.AI;

namespace BlogMigration;

/// <summary>Creates the blogger decision chain.</summary>
public class BloggerChain(IChatClient llm, ChatOptions chatOptions) : IBloggerChain
{
    public async Task<BloggerDecision> InvokeAsync(ResearchState state)
    {
        List<string> research = state.ResearchFindings;
        string researchText = research.Count > 0 ? string.Join("\n", research) : "No research yet.";
        int revision = state.RevisionNumber;
        bool hasResearch = research.Count > 0;
        bool hasDraft = !string.IsNullOrWhiteSpace(state.Draft);
        string review = state.ReviewNotes;

        if (review.ToUpperInvariant().Contains("APPROVED") && hasDraft)
        {
            Console.WriteLine("Blogger: Draft approved, ending workflow");
            return new BloggerDecision("END", "Report approved and complete");
        }

        if (!hasResearch)
        {
            Console.WriteLine("Blogger: No research yet, directing to researcher");
            return new BloggerDecision("researcher", $"Research the topic: {state.MainTask}");
        }

        if (hasResearch && !hasDraft)
        {
            Console.WriteLine("Blogger: Have research, creating first draft");
            return new BloggerDecision("author", "Write the first draft based on research findings");
        }

        if (hasDraft && string.IsNullOrEmpty(review))
        {
            Console.WriteLine("Blogger: Have draft, sending to reviewer");
            return new BloggerDecision("reviewer", "Prepare draft for review");
        }

        if (!string.IsNullOrEmpty(review) && !review.ToUpperInvariant().Contains("APPROVED") && revision <= 4)
        {
            Console.WriteLine($"Blogger: Revision {revision}, sending back to author");
            return new BloggerDecision("author", "Revise the draft based on review feedback");
        }

        // Max revisions reached
        if (revision >= 4)
        {
            Console.WriteLine("Blogger: Max revisions reached! Ending");
            return new BloggerDecision("END", "Maximum revisions reached! Finalizing report");
        }

        // LLM decision as fallback
        string prompt = Prompts.BloggerPromptTemplate
            .Replace("{main_task}", state.MainTask)
            .Replace("{research_findings}", researchText)
            .Replace("{draft}", string.IsNullOrEmpty(state.Draft) ? "No draft yet." : state.Draft)
            .Replace("{review_notes}", string.IsNullOrEmpty(review) ? "No review yet." : review)
            .Replace("{revision_number}", revision.ToString());

        try
        {
            ChatResponse response = await llm.GetResponseAsync(prompt, chatOptions);
            string content = response.Text;

            // Try to parse JSON
            string text = content.Trim();
            if (text.StartsWith("```"))
            {
                IEnumerable<string> lines = text.Split('\n').Where(l => !l.TrimStart().StartsWith("```"));
                text = string.Join("\n", lines);
            }
            text = text.Trim();

            BloggerDecision? decision = JsonSerializer.Deserialize<BloggerDecision>(text);

            if (decision is not null && !string.IsNullOrEmpty(decision.NextStep))
            {
                return decision;
            }
        }
        catch (Exception e)
        {
            Console.WriteLine($"LLM parsing error: {e.Message}");
        }

        // Final fallback - continue with author
        Console.WriteLine("Blogger: Using final fallback - continuing with author");
        return new BloggerDecision("author", "Continue with draft creation");
    }

It is in this class that we can create the BloggerNode, which will be used in the same way as it is in the Python example:


    /// <summary>Blogger decides the next step.</summary>
    public async Task<ResearchState> BloggerNodeAsync(ResearchState state)
    {
        Console.WriteLine("\n>>>Blogger");

        BloggerDecision decision = await InvokeAsync(state);

        string nextStep = string.IsNullOrEmpty(decision.NextStep) ? "researcher" : decision.NextStep;
        string taskDesc = string.IsNullOrEmpty(decision.TaskDescription) ? "Continue work" : decision.TaskDescription;

        Console.WriteLine($"Decision: {nextStep}");
        Console.WriteLine($"Task: {taskDesc}");

        state.NextStep = nextStep;
        state.CurrentSubTask = taskDesc;
        return state;
    }
}

Notice the use of a BloggerDecision object. Let’s create that in a file BloggerDecision.cs

using System.Text.Json.Serialization;

namespace BlogMigration;

public record BloggerDecision(
    [property: JsonPropertyName("next_step")] string NextStep,
    [property: JsonPropertyName("task_description")] string TaskDescription);

Program.cs has more to do than we’ve seen so far. Among other things, it will instantiate a BloggerChain object, which we’ll see after we create the Author, Researcher, and Reviewer related classes.

We’ll tackle the Author files in the next installment.

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

Database Performance: Making Entity Framework Queries Faster

1 Share
The article discusses the advantages of using compiled queries in Entity Framework, especially for frequently executed and complex queries.
Read the whole story
alvinashcraft
25 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

How to Handle Errors the Right Way in Flutter: A Practical Guide to Sealed Classes, Records, and Result Types

1 Share

I used to think I was handling errors well in my Flutter apps. I had try/catch blocks everywhere. I was catching exceptions, logging them, and showing error messages to users. It felt solid.

Then I started looking more carefully at what was actually happening in production. There were silent failures I never knew about. Functions that could throw but nothing in the type system warned you about it. Error handling scattered inconsistently across the codebase — some places caught errors, others didn't.

A junior developer on the team added a new API call and forgot the try/catch entirely, and nobody caught it in review because there was nothing in the code that said "this function can fail."

That's when I started taking error handling seriously as an architectural decision, not just a defensive habit.

This article covers the patterns I now use in production Flutter apps — Result types, sealed classes, Dart 3 records, and pattern matching — and how they work together to make errors visible, explicit, and impossible to ignore.

Table of Contents

Why try/catch Alone Isn't Enough

Try/catch works. I'm not saying it doesn't. For simple cases it's perfectly fine. But as your app grows, relying on try/catch as your primary error handling strategy creates a specific set of problems that only become obvious at scale.

The problem is invisibility.

When a function can throw an exception, there's nothing in its signature that tells you that. Look at this:

Future<User> getUser(String userId) async {
  final response = await dio.get('/users/$userId');
  return User.fromJson(response.data);
}

This function looks like it always returns a User. Nothing about its signature suggests it might fail. A developer calling this function has no idea whether to wrap it in a try/catch unless they read the implementation or have been burned by it before.

Now imagine this function is called in ten different places across your app. Some developers remember to handle errors. Others don't. There's no compiler warning, no lint rule, nothing to catch the inconsistency. The errors are invisible until a user reports a crash.

The second problem is that exceptions are contagious.

When a function throws, every caller has to handle it. And every caller of those callers. The error handling responsibility spreads outward through your codebase, often inconsistently. Some layers swallow exceptions silently. Others re-throw them. The flow of errors through your app becomes hard to reason about.

The third problem is that not all errors are exceptional.

A network request failing isn't an exceptional event in a mobile app. It's expected. Treating it as an exception — something abnormal that interrupts the normal flow — is the wrong mental model. It's a normal outcome that should be handled like any other outcome.

This is the core insight behind Result types: errors are values, not interruptions.

Errors as Values: the Core Idea

The idea is simple. Instead of a function either returning a value or throwing an exception, it always returns a value — but that value can represent either success or failure.

// Instead of this — may or may not throw
Future<User> getUser(String userId);

// We write this — always returns a result
Future<Result<User>> getUser(String userId);

Now the function signature is honest. It tells you "this operation can succeed or fail, and you have to deal with both." The compiler enforces that you handle both cases. There's no way to accidentally ignore the failure path.

This pattern comes from languages like Rust and Kotlin where it's built into the standard library. In Dart we build it ourselves — and with sealed classes and pattern matching in Dart 3, it's cleaner than ever.

Building a Result Type with Sealed Classes

Here's the Result type I use in production:

// result.dart

// Sealed means every possible subtype is defined
// right here in this file. The compiler knows
// there are exactly two possible outcomes —
// Success and Failure — and nothing else.
sealed class Result<T> {}

// Success carries the value we wanted.
// T is the type parameter — Result<User> means
// Success carries a User, Result<List<Post>> carries a list.
class Success<T> extends Result<T> {
  final T data;
  const Success(this.data);
}

// Failure carries an AppError describing what went wrong.
// We use a typed error class rather than a raw exception
// so the UI can make decisions based on the error type.
class Failure<T> extends Result<T> {
  final AppError error;
  const Failure(this.error);
}

Now we need a typed error class. Instead of passing raw exception messages around, we define the specific errors our app can produce:

// app_error.dart

// AppError is also sealed — every error type our app
// can produce is defined here. This makes it impossible
// to have an unhandled error type slip through.
sealed class AppError {}

// No internet connection
class NoInternetError extends AppError {}

// The server returned an error response
class ServerError extends AppError {
  final int statusCode;
  final String message;
  const ServerError({required this.statusCode, required this.message});
}

// The data came back in an unexpected format
class ParseError extends AppError {
  final String message;
  const ParseError(this.message);
}

// Something unexpected happened that we didn't anticipate
class UnknownError extends AppError {
  final String message;
  const UnknownError(this.message);
}

Now let's use this in a repository:

// post_repository.dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:dio/dio.dart';
import 'result.dart';
import 'app_error.dart';
import 'post.dart';

class PostRepository {
  final Dio _dio;
  PostRepository(this._dio);

  Future<Result<List<Post>>> getPosts() async {
    try {
      final response = await _dio.get(
        'https://jsonplaceholder.typicode.com/posts',
      );

      // Parse the response into a list of Post objects.
      // We wrap this in its own try/catch because parsing
      // can fail independently of the network call —
      // the API might return valid JSON but in an unexpected shape.
      try {
        final List<dynamic> data = response.data as List<dynamic>;
        final posts = data
            .map((json) => Post.fromJson(json as Map<String, dynamic>))
            .toList();

        // Wrap the success value in Success<List<Post>>
        // and return it. The caller receives a Result,
        // not a raw list, so they know they have to
        // check whether it succeeded or failed.
        return Success(posts);
      } catch (e) {
        return Failure(ParseError('Failed to parse posts: $e'));
      }
    } on DioException catch (e) {
      // Map Dio's exception types to our own AppError types.
      // This keeps Dio-specific types out of the rest of the app.
      // If we ever swap Dio for a different HTTP client,
      // only this file needs to change.
      if (e.type == DioExceptionType.connectionError) {
        return Failure(NoInternetError());
      }

      return Failure(
        ServerError(
          statusCode: e.response?.statusCode ?? 0,
          message: e.message ?? 'Server error',
        ),
      );
    } catch (e) {
      // Catch-all for anything unexpected
      return Failure(UnknownError(e.toString()));
    }
  }
}

Notice what changed. The function signature Future<Result<List<Post>>> is now honest. Anyone calling getPosts() knows they're getting a Result — they can't pretend it always succeeds. And the try/catch is contained entirely inside the repository. Nothing leaks out to the callers.

Dart 3 Records and What They Add

Before we get to pattern matching, it's worth talking about Dart 3 records because they pair naturally with Result types.

A record is a lightweight, anonymous object that groups multiple values together without needing to define a full class. Think of it as a quick way to return multiple values from a function.

// Before records — you needed a class or a Map
// to return multiple values
Map<String, dynamic> getUserInfo() {
  return {'name': 'Nicholas', 'age': 28};
  // No type safety — 'age' could be anything
}

// With records — type safe, no class needed
(String name, int age) getUserInfo() {
  return ('Nicholas', 28);
  // The compiler knows name is a String and age is an int
}

Records become useful in error handling when you need to return a value and some metadata alongside it:

// A function that returns a post and its fetch timestamp
Future<Result<(Post, DateTime)>> getPostWithTimestamp(
  String postId,
) async {
  try {
    final response = await _dio.get('/posts/$postId');
    final post = Post.fromJson(response.data);

    // The record (post, DateTime.now()) groups both values
    // without needing a wrapper class
    return Success((post, DateTime.now()));
  } catch (e) {
    return Failure(UnknownError(e.toString()));
  }
}

And consuming it:

final result = await repository.getPostWithTimestamp('1');

switch (result) {
  case Success(:final data):
    // Destructure the record directly in the pattern
    final (post, fetchedAt) = data;
    print('Got \({post.title} at \)fetchedAt');
  case Failure(:final error):
    print('Failed: $error');
}

Records are not essential for Result types but they remove the need for small helper classes that exist purely to carry two or three values together. I use them regularly in repository methods that need to return data alongside pagination cursors or cache metadata.

Pattern Matching on Errors

This is where everything comes together. Sealed classes plus pattern matching means the compiler forces you to handle every possible outcome. You can't accidentally ignore the failure case.

final result = await repository.getPosts();

switch (result) {
  // Named field pattern — extracts 'data' directly
  // from Success without a manual cast
  case Success(:final data):
    print('Got ${data.length} posts');

  case Failure(:final error):
    // Now pattern match on the error type
    // to give the user the right message
    switch (error) {
      case NoInternetError():
        print('No internet connection. Please check your connection.');
      case ServerError(:final statusCode, :final message):
        print('Server error \(statusCode: \)message');
      case ParseError(:final message):
        print('Something went wrong parsing the data: $message');
      case UnknownError(:final message):
        print('Unexpected error: $message');
    }
}

Both switch statements are exhaustive. If you add a new Result subtype and forget to handle it here, you get a compile error. Add a new AppError subtype and forget to handle it here, you get a compile error. The compiler is working as your quality control.

You can also use the when extension pattern for more concise handling:

// A helper extension that makes Result easier to consume
extension ResultExtension<T> on Result<T> {
  // Runs onSuccess if this is a Success,
  // runs onFailure if this is a Failure
  R when<R>({
    required R Function(T data) onSuccess,
    required R Function(AppError error) onFailure,
  }) {
    return switch (this) {
      Success(:final data) => onSuccess(data),
      Failure(:final error) => onFailure(error),
    };
  }

  // Returns the data if Success, null if Failure
  T? getOrNull() => switch (this) {
    Success(:final data) => data,
    Failure() => null,
  };

  // Returns true if this is a Success
  bool get isSuccess => this is Success<T>;

  // Returns true if this is a Failure
  bool get isFailure => this is Failure<T>;
}

Usage becomes very clean:

final result = await repository.getPosts();

final posts = result.when(
  onSuccess: (data) => data,
  onFailure: (error) => <Post>[],
);

Applying This to a Real Bloc Feature

Let's wire everything into a complete Bloc. We'll use the posts feature we built in the previous session and upgrade it to use Result types.

The states, now with sealed classes:

// post_state.dart

sealed class PostState {}

class PostInitial extends PostState {}

class PostLoading extends PostState {}

// Success state carries the posts directly
class PostLoaded extends PostState {
  final List<Post> posts;
  const PostLoaded(this.posts);
}

// Error state carries a typed AppError, not just a string.
// This means the UI can make decisions based on the
// error type — show a "no internet" message vs a
// "server error" message vs a "try again" message.
class PostError extends PostState {
  final AppError error;
  const PostError(this.error);
}

The Bloc:

// post_bloc.dart

class PostBloc extends Bloc<PostEvent, PostState> {
  final PostRepository _repository;

  PostBloc(this._repository) : super(PostInitial()) {
    on<LoadPosts>(_onLoadPosts);
  }

  Future<void> _onLoadPosts(
    LoadPosts event,
    Emitter<PostState> emit,
  ) async {
    emit(PostLoading());

    // getPosts() now returns Result<List<Post>>
    // We pattern match on the result directly —
    // no try/catch needed here because the repository
    // already handles all error cases and wraps them
    // in a Failure. The Bloc just reads the result.
    final result = await _repository.getPosts();

    switch (result) {
      case Success(:final data):
        emit(PostLoaded(data));
      case Failure(:final error):
        emit(PostError(error));
    }
  }
}

Notice there's no try/catch in the Bloc at all. The repository owns error handling. The Bloc just reads the Result and emits the right state. It's clean, simple, and each layer doing exactly one job.

The UI:

// post_screen.dart

BlocBuilder<PostBloc, PostState>(
  builder: (context, state) {
    return switch (state) {
      PostInitial() => const Center(
          child: Text('Press the button to load posts'),
        ),

      PostLoading() => const Center(
          child: CircularProgressIndicator(),
        ),

      PostLoaded(:final posts) => ListView.builder(
          itemCount: posts.length,
          itemBuilder: (context, index) {
            final post = posts[index];
            return ListTile(
              leading: Text('${post.id}'),
              title: Text(post.title),
              subtitle: Text(post.body),
            );
          },
        ),

      // Pattern match on the error type to show
      // the right message for each specific error.
      // This is something try/catch cannot give you —
      // typed, structured errors that the UI can act on.
      PostError(:final error) => Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                switch (error) {
                  NoInternetError() =>
                    'No internet connection. Please check your connection.',
                  ServerError(:final statusCode) =>
                    'Server error ($statusCode). Please try again.',
                  ParseError() =>
                    'Something went wrong. Please try again.',
                  UnknownError() =>
                    'An unexpected error occurred.',
                },
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: () {
                  context.read<PostBloc>().add(LoadPosts());
                },
                child: const Text('Try again'),
              ),
            ],
          ),
        ),
    };
  },
)

The UI now shows a different message for each error type. A user with no internet gets a different message than a user who hit a server error. That's a much better user experience than a generic "something went wrong". And it comes directly from having typed errors rather than raw exception messages.

When This Approach Is Worth It and When It Isn't

I want to be honest here, because I've seen developers over-engineer simple things in the name of good architecture.

Use Result types when:

  • The function can fail in multiple distinct ways that the caller needs to handle differently

  • You're building a repository or service layer that multiple features depend on

  • You're working in a team where inconsistent error handling is a real problem

  • The feature involves money, user data, or anything where silent failures are dangerous

Stick with try/catch when:

  • It's a simple, one-off operation in a small feature

  • The error handling is the same regardless of what went wrong: show a message, log it, done

  • You're prototyping or in early development and the architecture is still changing

  • The added complexity isn't justified by the size of the codebase

The Result type pattern adds ceremony. There's no point denying that. A simple try/catch is less code. The tradeoff is that try/catch is invisible — nothing enforces that callers handle errors. Result types are explicit — the type system enforces it.

For production apps that serve real users and have more than one developer working on them, that explicitness is worth the extra code. For a side project you're building alone, it might be overkill.

End-to-End Example

Here's everything together in one complete feature. Copy this into a new Flutter project and run it.

Folder structure:

lib/
  core/
    result.dart
    app_error.dart
  models/
    post.dart
  data/
    post_repository.dart
  bloc/
    post_bloc.dart
    post_event.dart
    post_state.dart
  ui/
    post_screen.dart
  main.dart

result.dart:

sealed class Result<T> {}

class Success<T> extends Result<T> {
  final T data;
  const Success(this.data);
}

class Failure<T> extends Result<T> {
  final AppError error;
  const Failure(this.error);
}

extension ResultExtension<T> on Result<T> {
  R when<R>({
    required R Function(T data) onSuccess,
    required R Function(AppError error) onFailure,
  }) {
    return switch (this) {
      Success(:final data) => onSuccess(data),
      Failure(:final error) => onFailure(error),
    };
  }
}

app_error.dart:

sealed class AppError {}

class NoInternetError extends AppError {}

class ServerError extends AppError {
  final int statusCode;
  final String message;
  const ServerError({required this.statusCode, required this.message});
}

class ParseError extends AppError {
  final String message;
  const ParseError(this.message);
}

class UnknownError extends AppError {
  final String message;
  const UnknownError(this.message);
}

post.dart:

class Post {
  final int id;
  final String title;
  final String body;
  final int userId;

  const Post({
    required this.id,
    required this.title,
    required this.body,
    required this.userId,
  });

  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      id: json['id'] as int,
      title: json['title'] as String,
      body: json['body'] as String,
      userId: json['userId'] as int,
    );
  }
}

post_repository.dart:

import 'package:dio/dio.dart';
import '../core/result.dart';
import '../core/app_error.dart';
import '../models/post.dart';

class PostRepository {
  final Dio _dio;
  PostRepository(this._dio);

  Future<Result<List<Post>>> getPosts() async {
    try {
      final response = await _dio.get(
        'https://jsonplaceholder.typicode.com/posts',
      );

      try {
        final List<dynamic> data = response.data as List<dynamic>;
        final posts = data
            .map((json) => Post.fromJson(json as Map<String, dynamic>))
            .toList();
        return Success(posts);
      } catch (e) {
        return Failure(ParseError('Failed to parse posts: $e'));
      }
    } on DioException catch (e) {
      if (e.type == DioExceptionType.connectionError) {
        return Failure(NoInternetError());
      }
      return Failure(
        ServerError(
          statusCode: e.response?.statusCode ?? 0,
          message: e.message ?? 'Server error',
        ),
      );
    } catch (e) {
      return Failure(UnknownError(e.toString()));
    }
  }
}

post_event.dart:

sealed class PostEvent {}

class LoadPosts extends PostEvent {}

post_state.dart:

import '../core/app_error.dart';
import '../models/post.dart';

sealed class PostState {}

class PostInitial extends PostState {}
class PostLoading extends PostState {}

class PostLoaded extends PostState {
  final List<Post> posts;
  const PostLoaded(this.posts);
}

class PostError extends PostState {
  final AppError error;
  const PostError(this.error);
}

post_bloc.dart:

import 'package:flutter_bloc/flutter_bloc.dart';
import '../core/result.dart';
import '../data/post_repository.dart';
import 'post_event.dart';
import 'post_state.dart';

class PostBloc extends Bloc<PostEvent, PostState> {
  final PostRepository _repository;

  PostBloc(this._repository) : super(PostInitial()) {
    on<LoadPosts>(_onLoadPosts);
  }

  Future<void> _onLoadPosts(
    LoadPosts event,
    Emitter<PostState> emit,
  ) async {
    emit(PostLoading());

    final result = await _repository.getPosts();

    switch (result) {
      case Success(:final data):
        emit(PostLoaded(data));
      case Failure(:final error):
        emit(PostError(error));
    }
  }
}

post_screen.dart:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/post_bloc.dart';
import '../bloc/post_event.dart';
import '../bloc/post_state.dart';
import '../core/app_error.dart';

class PostScreen extends StatelessWidget {
  const PostScreen({super.key});

  String _errorMessage(AppError error) {
    return switch (error) {
      NoInternetError() =>
        'No internet connection. Please check your connection.',
      ServerError(:final statusCode) =>
        'Server error ($statusCode). Please try again.',
      ParseError() => 'Something went wrong. Please try again.',
      UnknownError() => 'An unexpected error occurred.',
    };
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Posts')),
      body: BlocBuilder<PostBloc, PostState>(
        builder: (context, state) {
          return switch (state) {
            PostInitial() => const Center(
                child: Text('Press the button to load posts'),
              ),
            PostLoading() => const Center(
                child: CircularProgressIndicator(),
              ),
            PostLoaded(:final posts) => ListView.builder(
                itemCount: posts.length,
                itemBuilder: (context, index) {
                  final post = posts[index];
                  return ListTile(
                    leading: Text('${post.id}'),
                    title: Text(post.title),
                    subtitle: Text(post.body),
                  );
                },
              ),
            PostError(:final error) => Center(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Text(_errorMessage(error)),
                    const SizedBox(height: 16),
                    ElevatedButton(
                      onPressed: () {
                        context.read<PostBloc>().add(LoadPosts());
                      },
                      child: const Text('Try again'),
                    ),
                  ],
                ),
              ),
          };
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<PostBloc>().add(LoadPosts()),
        child: const Icon(Icons.download),
      ),
    );
  }
}

main.dart:

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/post_bloc.dart';
import 'data/post_repository.dart';
import 'ui/post_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Result Type Demo',
      home: BlocProvider(
        create: (_) => PostBloc(PostRepository(Dio())),
        child: const PostScreen(),
      ),
    );
  }
}

Final Thoughts

None of this is about being clever or following a pattern for its own sake. It's about making errors visible.

The fundamental problem with try/catch as your only error handling tool is that it hides the possibility of failure behind a normal-looking function signature. Result types surface that possibility in the type system, where the compiler can help you handle it consistently.

The combination of sealed classes, typed errors, pattern matching, and Dart 3 records gives you a system where:

  • Functions are honest about what they can return

  • Every error type is handled explicitly

  • Adding a new error type automatically breaks every switch that doesn't handle it

  • The UI can show the right message for the right error

I wish I'd built my first production app this way. It would have saved me a lot of time tracking down silent failures and inconsistent error states.

If you're already comfortable with try/catch and want to take your error handling to the next level, start small. Add a Result type to one repository. See how it feels. The pattern tends to spread naturally once you experience the clarity it brings.



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

15 Fascinating Fictional Fathers Every Writer Should Know

1 Share

Discover 15 fascinating fictional fathers who reveal the many ways father figures can shape characters, plots, and themes.

We’ve celebrated previous Father’s Days with The Top 12 Literary Quotes About Fathers and Finding A Role For Fictional Fathers. In this post, I decided to list 15 of the most interesting fathers from the books I’ve read.

I hope some of these famous fathers in fiction inspire you to create memorable fathers in the books you write.

15 Fascinating Fathers In Fiction

The Bad Fathers In Fiction

  1. Humbert Humbert in Lolita by Vladimir NabokovHumbert is one of the most repulsive stepfathers in fiction. He marries Charlotte Haze so that he can get close to her twelve-year-old daughter, Dolores, who he nicknames ‘Lolita’. When Charlotte dies, he commits statutory rape with Dolores.
  2. King Lear in King Lear by William Shakespeare. Lear’s decision to test his three daughters to find out who loves him most and to determine the size of their inheritance is a bad idea. He favours his youngest daughter, Cordelia, but she angers him when she refuses to play his game. His other daughters are happy to flatter him. They get the land, and then they plot his downfall.
  3. Jack Torrance in The Shining by Stephen King. As the belligerent alcoholic and possibly possessed father in The Shining, Jack is terrifying. After trying to beat his wife, Wendy, to death with a croquet mallet, and kill his son, Danny, he comes to his senses and allows them to escape, moments before the hotel’s boiler explodes, killing him.
  4. Pap Finn in The Adventures of Huckleberry Finn by Mark Twain. Huck sets off in a little boat across the Mississippi because of his father. Pap Finn is an uncouth, abusive drunk. Luckily Huck escapes and sets off on great adventures.
  5. Henry Wingo in The Prince of Tides by Pat Conroy. Henry is successful shrimper who spends his money on ludicrous businesses that leave his family destitute. He raises his children, Tom, Savannah, and Luke, by beating them. Their proud, status-hungry mother does not let her children speak about their father’s abuse.

Somewhere In Between

  1. George Darling is father to Wendy, Michael and John in J. M. Barrie’s Peter Pan. George Darling prefers to be respected by his children rather than liked by them. Darling has a stressful job and he can’t relate to his children’s stories of Neverland. However, George Darling wants what is best for his children.
  2. Mr Banks in Mary Poppins by P.L. Travers The typical middle-class patriarch, city banker George Banks is portrayed as a distant father in the Poppins series of books. Although gruff, he is loving towards his wife and children.
  3. Mr Bennett from Pride and Prejudice by Jane Austen. The long-suffering husband of Mrs Bennet and the father of five unmarried daughters, Mr Bennet uses sarcasm and wry humour to cope with the domestic dramas in the Bennet household. A fairly weak man, he mostly plays the part of a bemused bystander. He does love his daughters, particularly Elizabeth, and supports her decision to marry for love.
  4. Franklin Plaskett in Lionel Shriver’s We Need to Talk About Kevin. Franklin’s problem is that he indulges Kevin too much. He will not discipline him or acknowledge that he has a problem. He is severely punished for his ineffectual parenting.
  5. Ted Cole from A Widow For One Year by John Irving is a tragic figure. He is dealing with a loss that does not make him stronger. The first half of the novel focuses on the womanising children’s book writer when his daughter, Ruth is a child. The second half focuses on the adult Ruth trying to cope with the fallout of being her father’s daughter.

The Good Fathers In Fiction

  1. The Dad from The Road by Cormac McCarthy. The father loses everything as he tries to protect his son from the worst of a post-apocalyptic world. Even though he is dying, he does his best in the face of constant threats of attack, exposure, and starvation.
  2. Atticus Finch from To Kill A Mockingbird by Harper Lee. Atticus conducts himself with dignity as he shares important moral lessons with his children, Jem and Scout. The lawyer defends a black man accused of raping a white woman. Finch’s willingness to do what is right no matter the cost, has made him one of the most popular literary characters of all time.
  3. Joseph in La Gloire de mon père by Marcel Pagnol. Marcel’s father, Joseph, is a hard-working strongly atheist school teacher in Marseilles. During a summer break, Joseph and his brother-in-law, the very theistic and Roman Catholic Uncle Jules, decide to take their respective families to the country. Over the course of the summer, Marcel comes to respect and admire his strong, courageous father. I also loved Le château de mamere. Both books were love letters from a boy to his parents, to tradition, and to happy childhood memories.
  4. Mr Lancaster from The Fault in Our Stars by John Green. The father of cancer patient, Hazel Grace, influences Hazel’s world view. Even though she mocks his tendency to display emotions and cry during difficult times, she realises her father is a wise man. He is also not afraid to call her out when she’s out of line, and most importantly, he truly loves her mother.
  5. Jack Salmon is Susie Salmon’s father from The Lovely Bones by Alice Sebold. 14-year-old Susie disappears and evidence of her death is eventually uncovered. Narrated from the dead girl’s perspective, the novel shows how far a father will go to get to the bottom of his daughter’s fate.

The Last Word

Fictional fathers come in many forms. They can be kind or cruel, wise or foolish, present or absent. What they all do is affect the people around them and help shape the story. By studying these fathers, writers can learn how to create fascinating characters, relationships, and conflicts. (Another great read for writers is Finding A Role For Fictional Fathers.)

If you enjoyed this, you may also enjoy The 15 Most Memorable Mothers in Literature and this quiz about Fictional Fathers


by Amanda Patterson
© Amanda Patterson

If you enjoyed this blogger’s article, read:

  1. How Writers Use The Confidant As A Literary Device
  2. 10 Powerful Recurring Themes In Children’s Stories & Why They Matter
  3. What Is An Epistolary Novel? & Tips For Writing One
  4. What Is A Plot? – A Writer’s Resource
  5. How Writers Use The Antagonist As A Literary Device
  6. How Using A Timeline Can Help You Plot Your Novel
  7. 7 Really Good Reasons To Write A Memoir
  8. The Ultimate Checklist For Writing A Memoir
  9. How To Find Your Story’s Theme In 3 Simple Steps
  10. Why Embracing Your Inner Madness Makes You A Better Writer
  11. What Is A Story Goal? The Secret To A Strong Plot

Top Tip: Sign up for our free daily writing links.

The post 15 Fascinating Fictional Fathers Every Writer Should Know appeared first on Writers Write.

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

F# Weekly #25, 2026 — Expecto 11.1 & Fable 5.3

1 Share

Welcome to F# Weekly,

A roundup of F# content from this past week:

Microsoft News

Videos

Blogs

A short update about Oxpecker extension librariesmedium.com/@lanayx/meet…#fsharp #htmx #alpinejs #oxpecker

Vladimir Shchur (@lanayx.bsky.social) 2026-06-18T15:30:56.965Z

Highlighted projects

  • ScottArbeit/Grace — Grace Version Control System: a cloud-native, actor-model VCS built with F# and Orleans
  • litesig/litecoin-multisig — Seven F# modules compiled to JavaScript via Fable, covering the full stack from BIP39 seed phrases to signed-and-broadcast PSBTs.
  • HelgeSverre/mire — A retained-mode F# TUI runtime with diff-based rendering and Elmish/TEA loop for .NET 10
  • gnrkr789/CrawlSage — An F#-first web crawling & scraping framework for .NET with HTML parsing DSL and polite-by-default crawl ops
  • agentruntimecontrolprotocol/fsharp-sdk — F# / .NET reference SDK for ARCP (Agent Runtime Control Protocol)

New Releases

🚀 NBomber Studio 0.8.0 is out!lnkd.in/dQ6Jv8x8Highlights: • ⏰ Scheduled load test execution • ☸ Enhanced Kubernetes Container Spec support • 🔐 More flexible auth configuration#dotnet #loadtesting #performance #devtools #nbomber #csharp #fsharp #testing #QA

Anton Moldovan (@antyadev.bsky.social) 2026-06-19T19:32:12.977Z

That’s all for now. Have a great week.

If you want to help keep F# Weekly going, click here to jazz me with Coffee!

Buy Me A Coffee





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

v2026.6.9

1 Share

v2026.6.9

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