今日已更新 256 条资讯 | 累计 40701 条内容
关于我们

标签:#flutter

找到 40 篇相关文章

AI 资讯

What iOS Build Tools Are Available: From xcodebuild to KXApp Compilation Solutions

The build process for iOS applications involves multiple technical stages, including source code compilation, resource packaging, code signing, and IPA generation. Xcode is the first choice for most iOS developers, but build tools are not limited to Xcode alone; choosing the right tool for different development scenarios can significantly improve efficiency. xcodebuild: The Command-Line Version of Xcode xcodebuild is a command-line build tool bundled with Xcode that compiles and packages without opening Xcode. Common commands include: xcodebuild build to build the project, xcodebuild archive to create an Archive, and xcodebuild -exportArchive to export an IPA. xcodebuild is suitable for integration into CI/CD. CI/CD platforms such as Jenkins, GitHub Actions, and GitLab CI can invoke xcodebuild to perform automated builds. Its downside is that there are many parameters, with over a dozen common parameter combinations, requiring time for initial configuration. Moreover, xcodebuild depends on the Xcode environment and can only be used when Xcode is installed on a Mac. Fastlane: An Automated Build Tool Fastlane, written in Ruby, is an automation toolchain built on top of xcodebuild. It defines the workflows for building, testing, signing, and releasing through a Fastfile configuration file. For instance, lane :release defines a release pipeline that executes operations such as incrementing the version number, compiling, packaging, and uploading to TestFlight in sequence. Fastlane's match feature manages certificates and provisioning profiles, solving the problem of certificate synchronization in team collaboration. gym encapsulates the complex parameters of xcodebuild, allowing an IPA to be generated with a single command. Fastlane also has a rich plugin ecosystem covering push notifications, screenshots, and metadata management. Project Management with CocoaPods and XcodeGen CocoaPods handles the integration of third-party dependencies. Declare the dependent libraries

2026-09-08 原文 →
AI 资讯

Delivering messages with no internet, no servers, and no SIM

Every messenger you use has a hidden dependency: a working network path to a datacenter. Drop into a basement, a packed stadium, a moving train through a tunnel, an exam hall with jammers, or a remote area with no plan, and the app is just a spinner. The people you want to reach are often standing a few meters away, but your message still has to travel to a server on another continent and back. When that path is gone, so is the app. Kabootar is my attempt to remove that dependency entirely. It is a messenger with no backend at all. Your phone forms a peer-to-peer mesh with other phones nearby, and messages hop device to device over Bluetooth and Wi-Fi until they reach the recipient. No internet, no servers, no SIM. It is built in Flutter, and the routing core is plain Dart. The core idea: delay-tolerant networking The insight that makes this work is refusing to assume the recipient is reachable right now . Normal networking is connection-oriented: open a path end to end, then send. If there is no path, there is no delivery. Kabootar instead treats the network as a delay-tolerant network (DTN). A message does not need a live end-to-end path at the moment you hit send. It needs a chain of carriers that will exist over time . You hand your message to whoever is nearby. They hold onto it, carry it as they walk around, and pass it along to the next phone they meet. Eventually a carrier bumps into the recipient and the message lands, even if that is minutes later and both you and the recipient have long since walked away. This is store-and-forward, the same shape as a durable, at-least-once message queue, except the queue is running across a swarm of phones instead of inside a datacenter. How a message actually travels The routing strategy is epidemic routing: flooding. When you send a message, it spreads to everyone in range like a rumor. Each device that receives it re-broadcasts it onward, so the message replicates through the crowd, taking every path at once. That red

2026-09-07 原文 →
AI 资讯

What It Actually Takes to Run a Cross-Border Marketplace: Six Years of Shpper

Shpper is a cross-border personal-shopping marketplace. A buyer wants something they cannot get where they live. A traveller is already flying that route with unused luggage space. The platform introduces them, holds the money until the item arrives, and turns an empty few kilos in someone's suitcase into a delivery network. I am the CTO. I own the platform end to end — the Flutter apps for both sides, the backend, payments and escrow, identity verification, and the release pipeline. We are on version 14.2.0, rated 4.64 on the App Store from 72 ratings. Fourteen major versions is enough distance to say something useful about what this kind of product actually demands. Almost none of the hard parts were the ones I expected. You are not building one app The first structural fact about a two-sided marketplace is that "the app" is two products with opposed interests, and you ship both. The buyer wants their item cheaply, quickly, and with certainty it will arrive. The traveller wants to be paid well, carry as little risk as possible, and not be blamed for customs. Those are not complementary desires. Every feature has to be designed twice, from two points of view, and any change to the shared middle — the request, the offer, the trip — reaches into both. This has a consequence people underestimate: your release cadence is bounded by the slower side. A change to how offers work is not shipped when the buyer app ships. It is shipped when both apps are live, on both platforms, and enough of both populations have updated. Mobile app review is not a build step you can optimise away; it is a scheduling constraint that shapes how you design changes, which is why so much of the logic has to be able to change without a release. The hard problem is trust, and it is not a feature A marketplace's actual product is trust between strangers. Everything else is plumbing. Consider what the platform is asking. A buyer sends money for an item that does not exist yet, to be bought by someo

2026-09-06 原文 →
AI 资讯

CanvasKit Layout Traps: The Unbounded Constraint Bug That Only Blanks Release Builds

I shipped eight card and casino games to my portfolio in a single commit — solitaire, roulette, video poker, slots, baccarat, keno, war, higher-lower. All client-side Flutter web, all free, all deployed to Firebase Hosting in one push. flutter analyze was clean. I read the diff twice. The build succeeded. I deployed. Then I opened /games/roulette on the live site and got a page with a header, a subtitle, a bankroll readout, a spin button — and a completely blank rectangle where the betting board should have been. No red error screen. No console exception. No 404. Just an empty region the size of the thing that was supposed to be there, on a page where everything else rendered perfectly. The cause was one enum value: CrossAxisAlignment.stretch on a Row that, four widgets up the tree, was sitting inside a scroll view. In debug that combination throws a loud, well-written framework error. In release the assertion that produces that error doesn't exist, so nothing throws at all — the framework computes with infinity and paints nothing. A layout contract violation is not a type error, and no amount of static analysis is going to find it for you. This post is that bug in full, the family of unbounded-constraint traps it belongs to, why debug builds give you a false sense of safety, and the verification discipline I now refuse to skip. Eight games shipped, one board rendered nothing The symptom is worth describing precisely, because it's what makes this class of bug so slow to diagnose. The route loaded. The page scaffold — nav, page header, back link, related-games strip — was all there and correct. Analytics fired the pageview. The bankroll, the chip selector and the spin control rendered. Only the number grid, the largest single widget on the page, drew nothing at all. The space it occupied wasn't even collapsed to zero; it was just empty. The browser console was clean. Not "clean apart from a warning" — genuinely empty. Chrome DevTools' Elements panel showed what it al

2026-09-04 原文 →
AI 资讯

Taming Flutter Infinite Scroll: Why 3 Lines of async* Missed the Point, and How BlocSignal Fixes It

The Ubiquitous Infinite Scroll Pagination Bug Almost every Flutter engineer has encountered the dreaded infinite scroll race condition in production. The user opens a list, flings their thumb down the screen on a spotty cellular connection, and triggers multiple scroll notifications past the bottom threshold within milliseconds. Before the first asynchronous HTTP network request finishes, the scroll listener fires again. Suddenly, your list duplicates items, page counters jump ahead, or the state machine locks up entirely. Recently, mobile developer Ali Wajdan published a widely discussed article titled 3 Lines of Dart async* Code That Fixed My Infinite Scroll Pagination . In his article, Ali accurately diagnoses the root cause of standard pagination headaches: "Most Flutter pagination code I have seen, including my own for years, wraps a mutable state object around a scroll listener. A page counter, a loading boolean, a hasMore flag, and a fetch method the UI calls when it hits the scroll threshold. It works until two scroll events fire close together, or a rebuild triggers a second load before the first future resolves... It is a classic race condition, and it gets worse once the state lives across a page counter, a hasMore flag, and a loading flag that all need to stay in sync." To escape this trap, Ali suggested encapsulating pagination logic inside a Dart async* generator and consuming it with a StreamIterator : // The pattern proposed in Ali Wajdan's article Stream < List < Post >> fetchPostsPaginated ( String query ) async * { var page = 0 ; var hasMore = true ; while ( hasMore ) { final batch = await api . fetchPosts ( query , page: page ); hasMore = batch . isNotEmpty ; page ++ ; yield batch ; } } final iterator = StreamIterator ( fetchPostsPaginated ( query )); Future < List < Post >> loadNextPage () async { if ( ! await iterator . moveNext ()) return const []; return iterator . current ; } On the surface, moving mutable state into local generator variable

2026-09-03 原文 →
AI 资讯

Production Flutter Networking Without the Boilerplate: Reactive Repositories with BlocSignal

The Networking Architecture Dilemma in Production Flutter If you survey ten seasoned Flutter developers about how they structure networking in production, you will almost certainly see the same multi-tiered pipeline: ┌────────────────────────────────────────────────────────────────────────┐ │ Traditional Flutter Networking Pipeline │ ├────────────────────────────────────────────────────────────────────────┤ │ [Dio / HTTP Client] ─▶ [API Service] ─▶ [Repository Layer] ─▶ │ │ [Cubit / BLoC] ─▶ [UI Builders & Banners] │ └────────────────────────────────────────────────────────────────────────┘ The underlying architectural principles are sound: separation of concerns, testability, and isolating network transport details from UI widgets. However, in practice, this classical layered stack demands an enormous amount of repetitive boilerplate: Async State Union Ceremony : Defining four separate state classes ( Initial , Loading , Success(data) , Failure(error) ) or union types for every single API endpoint. Race Conditions & In-Flight Cancellation : When users type queries or switch tabs rapidly, requests finish out of order. Preventing stale responses requires complex Dio CancelToken plumbing or heavy rxdart switchMap streams. Offline Caching & "Stale-While-Revalidate" : Showing cached data on Frame 1 while fetching fresh updates in the background usually requires database synchronization and stream merging logic. The Repository vs. Controller Divide : Repositories hold data and caching logic, while BLoCs or Cubits hold reactive state. Because Dart only allows single inheritance, developers end up maintaining two separate class hierarchies connected by verbose dependency injection glue. With bloc_signals , we can preserve complete separation of concerns while eliminating 70% of the friction. Let us examine how to architect a modern, clean, production-ready networking layer using CubitSignalMixin , HydratedMixin , and .toAsyncBlocSignal() . ⚡ 1. Symmetrical Async Projection

2026-08-31 原文 →
AI 资讯

Mechanically Eliminating FutureBuilder & StreamBuilder: Universal Signal, Future, and Stream Adapters in BlocSignal

Making the Migration from In-View Asynchrony to Synchronous State Management Truly Mechanical After our recent discussions on why FutureBuilder and StreamBuilder are architectural anti-patterns when placed inside Flutter widget trees, I started thinking: how can we make it even easier—even completely mechanical—to convert from a FutureBuilder or StreamBuilder to a BlocSignalBuilder ? Every Flutter developer knows the history. Years ago, I recorded a video breaking down the hidden traps of placing asynchronous builders in UI views: Why you shouldn't put FutureBuilder in your build method . Even the original official Flutter video on FutureBuilder initially instantiated the network future directly inside the build() method, until I filed an issue to get it corrected (which is why the official Flutter YouTube video still proudly bears "Take 2" on its clapperboard!). The fundamental issue has never been that developers want bad architecture. The issue was friction . FutureBuilder was simply the path of least resistance. To do it "properly" in traditional state management, developers had to create an entire BLoC or Cubit, declare separate Event and State classes (or union types), write boilerplate event handlers, wire asynchronous repository methods, manage subscription lifecycles, and inject everything into the widget tree. With bloc_signals 1.1.0 , that friction disappears completely. We have introduced universal, symmetrical adapter extensions that allow any Dart Future , Stream , ReadonlySignal , or lifted primitive ( value.$ ) to adapt into a synchronous BlocSignalBase container with a single method call. 🧭 The Universal Dual-Track Mental Model When bridging asynchronous sources into synchronous state management, developers typically have one of two distinct intents: Raw Domain Values ( T ): You want raw domain objects (for example int , UserProfile , ThemeMode ) with zero wrapper ceremony, and you have an immediate default or fallback value for frame 0. Rich Asynch

2026-08-29 原文 →
AI 资讯

Why I built an app against fast swipe‑based social media: introducing SlowInk

Nowadays most social and pen‑pal apps are built around speed. Swipe left, swipe right, quick short messages, endless notifications. Platforms reward fast replies and surface‑level first impressions. We can chat with dozens of people every day, yet many of us still feel lonely. Connections are easy to start, but rarely grow deep. Even some existing pen‑pal apps gradually move toward swipe‑driven matching, focusing heavily on profile pictures instead of real thoughts. I wanted something different. What if we slow everything down? What if friendship starts from long, thoughtful letters rather than instant small‑talk? That is the original idea behind SlowInk . I am a solo indie developer building this application with Flutter. My goal was not to make another popular social product. I just wanted to solve a pain I felt myself: missing genuine, low‑pressure cross‑cultural communication. During development, I made several intentional product trade‑offs: No swipe matching mechanism. You will not judge people within one second by just looking at avatars. No real‑time instant chat. Communication happens through complete letters. You take your time writing, and others take their time replying. Reduce noisy notifications. There is no pressure to reply immediately. Focus on long‑form writing, for language exchange and sincere pen‑pal friendship. These choices brought technical challenges. Building a letter‑first social system is quite different from building typical instant‑messaging software. I spent a lot of time thinking about user privacy, spam prevention, and how to keep the atmosphere gentle for global users. Many features got cut in order to keep the core idea intact. SlowInk is still an early‑stage project. It is far from perfect. There are bugs to fix and features to polish. As a side‑project developer without large‑team support, every improvement moves forward little by little. If you feel tired of fast‑paced swipe‑based social media, or you enjoy writing and receiving

2026-08-25 原文 →
开发者

I built flutter_auditor — a zero-config CLI tool to audit Flutter apps for permissions, dead assets, security risks, and package hygiene

Shipping a Flutter app without auditing native permissions, release keystores, or asset bloat? To help Flutter developers catch hidden production risks before App Store/Play Store review, I built flutter_auditor — an open-source, zero-config CLI health and security inspector for Flutter & Dart. In just one terminal command (dart run flutter_auditor), it scans your project for: 🔒 17+ Automated Audits: Hardcoded API secrets & exposed .jks keystores Missing iOS Info.plist privacy description strings Unused heavy assets & broken 2.0x/3.0x image variant paths Dangerous manifest flags (android:debuggable="true", allowed cleartext traffic) Unused & transitive package dependencies Give it a try locally on your project and let me know what audits you'd like to see next! 👇 pub.dev: https://pub.dev/packages/flutter_auditor GitHub: https://github.com/thakaredipali/flutter_auditor

2026-08-20 原文 →
AI 资讯

Flutter Streaming UI: How the Typewriter Experience of AI Replies Is Built

The typewriter effect looks simple: characters appear one by one. But behind "skip animation", "no truncation", and "no performance regression" lies a whole set of engineering decisions. The implementation in this article is Flutter/Dart based, but the core semantic decisions — "skip ≠ abort" and "buffer and batch" — are framework-agnostic : Web's EventSource, and native/RN SSE clients, face the same choices. Prologue: a "skip typewriter" button that kept breaking In an AI narrative app (where the user influences an AI-driven interactive story by entering fate instructions), I built a "⏩ skip typewriter" button — users click it to see the full AI reply immediately instead of waiting for the text to appear character by character. The button went through three stages in the dev log: V1 : clicking does nothing — the callback fires, but the user experiences no change V2 : clicking truncates the content — the animation is gone, but the reply is incomplete too Final : clicking reveals the partial text immediately, while the LLM keeps generating the full reply in the background, which appears all at once when done Behind these three versions lie the three most common pitfalls in "streaming UI". This article breaks them down. 1. From SSE to screen: the streaming rendering pipeline Why the LLM "pops" text out The LLM's reply comes back in chunks via HTTP SSE (Server-Sent Events). A typical chunk looks like this: data: { "choices" :[{ "delta" :{ "content" : "Mephistopheles appears" }}]} data: { "choices" :[{ "delta" :{ "content" : "at the study door." }}]} data: [ DONE ] The interval between chunks is determined by the model's generation speed — tens of milliseconds when fast, possibly a full second when slow. That "character-by-character appearance" is what the user perceives as the typewriter animation. Why you can't update the UI on every chunk If you trigger a state update on every chunk, a reply of a few hundred characters can cause dozens or hundreds of UI rebuilds, whi

2026-08-18 原文 →
AI 资讯

Clean Architecture in Flutter with BLoC: A Practical Guide

Clean architecture in Flutter is the single biggest reason the production apps I ship stay maintainable after a year of feature churn. Over 4+ years building iOS and Android apps, I've watched "just put the logic in the widget" turn setState spaghetti into a codebase nobody wants to touch. This guide walks through how I actually split a Flutter app into domain , data , and presentation layers with BLoC — using one concrete feature so you can copy the structure into your own project today. I'll build a small "Todos" feature end to end: an entity, a use case, a repository with a DTO mapper, and a Cubit that drives the UI. The point isn't the todo list — it's the boundaries between layers and why each one earns its keep. Why clean architecture in Flutter pays off The core idea is the dependency rule : source-code dependencies point inward . The UI knows about the domain; the domain knows about nothing. Your business rules never import Flutter, Firebase, Dio, or Supabase. That inversion buys three things I care about on every project: Testability. Domain logic runs in plain Dart unit tests — no widget pump, no emulator, no network. Swappable infrastructure. Move from REST to GraphQL, or Firestore to a local SQLite cache, by rewriting one data-layer class. The domain and UI don't change. Parallel work. Once the domain contract exists, one person builds the API client while another builds the screen against a fake. Here's the layer breakdown I use, and what's allowed to live in each: Layer Knows about Contains Depends on Domain Nothing external Entities, repository interfaces , use cases Pure Dart only Data Domain + the outside world DTOs, mappers, repository implementations , data sources Domain Presentation Domain Blocs/Cubits, states, widgets Domain Notice the data and presentation layers both depend on domain, and domain depends on neither. That's the whole game. Folder structure that scales I organise by feature first, then by layer . A flat models/ , services/ , scr

2026-08-16 原文 →
AI 资讯

One-Shot UI Side Effects in BlocSignal: Snackbars, Dialogs, and Navigation Without State Pollution

Every Flutter developer has run into the Sticky State Dilemma . You build a login screen. When authentication fails, your state container emits an error. You catch it in your UI and show a SnackBar . Everything works—until the user rotates their phone, pulls down the notification shade, or types on the virtual keyboard. Suddenly, the widget tree rebuilds. The state container is still holding AuthErrorState("Invalid password") . The UI listener fires again. And a duplicate snackbar appears out of nowhere. In this article, we’ll explore why domain state machines struggle with transient UI events, how the classic BLoC community worked around this with package:bloc_presentation , and how BlocSignal lets you handle one-shot side effects cleanly with zero additional package dependencies . 1. The Root Problem: Persistent State vs. Ephemeral Actions State management in Flutter is designed to model persistent truth over time: Is the user logged in? AuthState.authenticated(user) Is data loading? TodoState.loading What is the cart total? $49.99 Persistent state answers: "What is the system's current condition?" In contrast, UI presentation actions are ephemeral pulses : Show a brief SnackBar toast. Pop up an alert confirmation dialog. Push a new route on the Navigator stack. Vibrate the haptic motor. These actions answer: "What just happened that requires a one-time reaction?" ┌────────────────────────────────────────────────────────┐ │ State vs. Effects │ ├────────────────────────────┬───────────────────────────┤ │ Persistent State │ Ephemeral Side-Effect │ ├────────────────────────────┼───────────────────────────┤ │ • Survived by UI rebuilds │ • Consumed once & gone │ │ • Represented in signals │ • Triggered by an event │ │ • Backed by equality diffs │ • Zero domain state footprint │ └────────────────────────────┴───────────────────────────┘ 2. The Legacy Workarounds (And Their Hidden Costs) Historically in package:bloc and package:flutter_bloc , developers used one of three

2026-08-16 原文 →
AI 资讯

Dogfooding BlocSignal on the Web: Building a 100K Ops/sec Reactive App with Jaspr and Dart 3.13

Building Pure Dart Web Apps Without Compromise When developers evaluate Dart for the web, they typically face a stark tradeoff: Flutter Web : Exceptional for canvas-driven applications, design systems, and cross-platform desktop/mobile parity—but heavy for content-first landing pages, docs, and fast-loading SEO sites. Jaspr Web : A lightweight, component-driven framework that compiles pure Dart to HTML and CSS with instant first paint and full search engine indexing. When we built the official documentation and showcase site for BlocSignal , we knew Jaspr was the perfect foundation. But like many engineers diving into a new UI paradigm, our initial implementation took a shortcut: we used raw StatefulComponent lifecycles and manual .subscribe() callbacks to wire up our state machines. It worked—but it wasn't idiomatic. In this behind-the-scenes case study, we walk through the process of dogfooding bloc_signals_jaspr across blocsignal.dev , replacing manual subscription glue with declarative consumer components, achieving 100,000 operations/sec in compiled JavaScript , and exploring the sheer developer ergonomics of Dart 3.13 primary constructors . The "Manual Subscription Trap": Why Raw .subscribe() Fails at Scale In classic Flutter or Jaspr development, when you create a state machine without framework-level consumer widgets, you might be tempted to subscribe inside initState() : // ❌ THE ANTI-PATTERN: Manual subscription glue in StatefulComponent class LiveVisualizerState extends State < LiveVisualizer > { late final LiveCounterBloc _bloc ; @override void initState () { super . initState (); _bloc = LiveCounterBloc (); // ⚠️ Flaw 1: Every state change triggers a full component setState _bloc . state . subscribe (( _ ) { if ( mounted ) setState (() {}); }); } @override void dispose () { // ⚠️ Flaw 2: Manual dispose tracking _bloc . close (); super . dispose (); } } While this appears harmless in a simple counter demo, it introduces three severe architectural flaws:

2026-08-15 原文 →
产品设计

Code Signing for Android/iOS

Every dev course teaches you to build the app. Almost none teach you how to actually ship it. I found that out the hard way on my first job — no signing, no publish, no exceptions. So I wrote the full setup, both platforms, both ways: → Android: keystore via CLI and Android Studio's GUI → iOS: manual signing (certs, App IDs, provisioning profiles) AND automatic signing → The exact steps before every archive/build Full walkthrough → https://medium.com/@smitp7502/from-keystore-to-app-store-understanding-code-signing-for-android-ios-30671b5fd2a2 flutter #android #ios #mobiledev

2026-08-12 原文 →
AI 资讯

Unit Testing in BlocSignal: The Practical Handbook

A Practical Guide to Faster, Deterministic Flutter & Dart Unit Testing If you’ve ever written unit tests for classic package:bloc applications using bloc_test , you know the drill: build your BLoC, dispatch an event in act , and assert state emissions in expect . Under the hood, classic BLoC processes state updates asynchronously via Dart microtask-queue Streams . While robust, testing asynchronous streams can introduce microtask timing headaches, race conditions, or the need to drain queues or use fakeAsync when testing complex side-effects. In BlocSignal , state updates propagate synchronously . Calling emit(newState) updates the underlying signal graph in the exact same call stack frame. This handbook is a practical, recipe-based guide to testing BlocSignal and CubitSignal applications using package:bloc_signals_test . Whether you’re coming from classic BLoC or brand new to Signals, this guide shows you how to test every scenario cleanly—and why it’s significantly easier than classic stream-based testing. 🤖 AI Assistant Tip : Working with an AI coding assistant (like Antigravity, Gemini CLI, or Cursor)? The official bloc-signals plugin includes a pre-built testing skill ( plugins/bloc-signals/skills/bloc-signals/ ) that automatically teaches your AI assistant these exact testing conventions, observer scoping rules, and declarative blocSignalTest patterns! 🛠️ Quick Reference: BLoC Streams vs. BlocSignal Testing Testing Task Classic BLoC ( package:bloc_test ) BlocSignal ( package:bloc_signals_test ) Why it’s easier in BlocSignal Execution Environment Often requires flutter test engine Pure dart test execution Blazing Speed : Business logic tests run in pure Dart CLI without booting Flutter UI engine. Simple State Assertions Requires async stream listener or blocTest Direct expect(cubit.state, 1) or blocSignalTest Synchronous : State updates on the next line of code without microtask delay. Failure Diagnostics Legacy Instance of 'CounterCubit' Built-in toString() :

2026-08-09 原文 →
AI 资讯

A 500-Line Flutter Login Test Became One Promt

Lets start with a bit of back story. I am a full stack developer. Developer being the keyword here, not a QA developer. But in my current role, I was recently asked to come up with a testing suite for the web application and the Flutter app I was managing and maintaining. At that time, I didn’t have anything better to do and thought this would be a fun little project to work on for a couple of weeks. Boy o boy, I was wrong. People in QA are so opinionated. Everyone has their preferred framework, structure, naming convention, abstraction, folder structure and a very strong opinion about why your approach is wrong. Starting with the industry best practices I started by trying to follow the trends and best practices used in the industry. Page Object Models, reusable helpers, proper assertions and all the usual bits and bobs. For the web application, which was built with React, I chose Playwright. For the Flutter app, I went with integration_test . Sounded simple enough. The login test that took three hours The first test I tried to write was a simple login flow. Open the application Enter the username and password Press the login button Wait for the dashboard Easy, right? It took me ages. And by ages, I mean roughly three hours just to get the web test to pass reliably. The actual Playwright test ended up being around 300 lines once I included the boilerplate, setup, selectors, assertions, waits, Page Object Model structure and everything else needed around the actual journey. Then came the Flutter app. That one was worse. The app has its own custom way of starting different flavors, and both the web application and Flutter app are white-labelled products. That means there are a lot of variations to cover. Different branding, configurations, screens and sometimes slightly different user journeys. Before I could even test the login flow, I needed a pile of setup code just to launch the correct version of the app. The Flutter test eventually went beyond 500 lines, includ

2026-08-06 原文 →
AI 资讯

How I Made Features in a Large Flutter App Actually Removable

"Just delete the features you don't need" is the easiest thing in the world to write in a README, and the hardest to make true. I hit this building a Flutter app with six verticals in one codebase — marketplace, ride-hailing, car rentals, social feed, chat, wallet. Deleting one should have been simple. It wasn't, because every feature had tendrils: a route in the central route table a tab hardcoded in the app shell a button on the home screen a service registered in main() Remove the feature folder and you get a wall of compile errors from files that have nothing to do with it. Here's what actually worked. Make three things data instead of code 1. Routes Each feature exposes its own routes from its own folder: class WalletModule extends AppModule { const WalletModule (); @override String get id = > 'wallet' ; @override List < GetPage > get pages = > [ GetPage ( name: AppRoutes . wallet , page: () = > const WalletScreen ()), ]; } The app's route table becomes a composition: static final routes = < GetPage >[ .. . _centralRoutes , .. . ModuleRegistry . pages , ]; Adding or removing a feature stops being an edit to a shared file. It's one line in a registry. 2. Bottom-navigation tabs This one surprised me. My app shell imported the feed widget directly: // before — the always-present shell depends on an optional feature import '../feed/feed_tab.dart' ; The shell ships in every build. That import meant the social feature could never be removed. So a tab became a small data class that a feature contributes: class ShellTab { final String id ; final int order ; // core tabs use 10/30/40 final IconData icon ; final String labelKey ; final Widget Function () builder ; } Social contributes its tab at order 20, slotting between home and alerts without the shell knowing it exists. The shell merges its own tabs with ModuleRegistry.shellTabs and sorts. 3. Entry points Home screens linked to feature screens with Get.toNamed(...) . Named routes are already decoupled — no import nee

2026-08-04 原文 →
AI 资讯

Porting 16 BLoC & Signal Benchmark Apps to BlocSignal: Elevating Flutter UX & DX

🚀 Ported Example Benchmark Suite Live on blocsignal.dev ! If you’ve been evaluating BlocSignal —the monorepo package bridging the predictable event-driven BLoC architecture with Rody Davis's reactive signals v7 primitives—we’ve got something big to share. We just launched a dedicated Ported Example Suite containing 16 full-featured, runnable benchmark applications adapted directly from the official felangel/bloc (10 apps) and rodydavis/signals.dart (6 apps) example repositories. 🌐 Explore the Live Benchmark Suite : https://blocsignal.dev/ported-examples 💡 Why Port These Benchmark Apps? State management benchmarks are best understood through real-world applications. By porting these established examples 1-to-1, developers can compare BlocSignal side-by-side with original implementations to see concrete architectural benefits: 1. Synchronous Frame Updates (Better Test DX) In classic BLoC, state updates propagate asynchronously over microtask streams. In BlocSignal , emit() updates state synchronously on the exact same frame . This eliminates microtask queue latency, making widget building feel instant and allowing unit tests to assert expect(bloc.stateValue, ...) without pumpAndSettle or stream delays. 2. Reactive computed() Derivations (Zero Event Plumbing) In complex apps like Todos or Dynamic Forms , classic BLoC often requires dispatching intermediate filter events or wiring up CombineLatestStream . With BlocSignal , you declare late final ReadonlySignal<List<Todo>> filteredTodos = computed(...) right inside the constructor. When state changes, downstream signals derive updated values reactively and lazily. 3. Streamless Event Concurrency Transformers (Lower Overhead) Event concurrency transformers ( sequential() , droppable() , restartable() ) in BlocSignal are implemented as streamless higher-order functions using pure Dart Mutex locks. You get full request-cancellation and debouncing capabilities (e.g. in GitHub Search ) with zero RxStream memory allocations .

2026-08-03 原文 →
AI 资讯

Stacked Pull Requests: how I would split a Flutter feature into four reviewable layers

The problem is not the code I build Flutter apps. A normal feature touches four layers at once. Models and json parsing Repository and API client State management Screens and widgets When I ship that as one branch, the pull request is somewhere between 1,000 and 4,000 lines. And then one of two things happens. Either the reviewer opens 40 files, scrolls for ten minutes, and writes "looks good". That is not a review. That is a signature. Or the reviewer does the job properly, takes three days, and leaves a comment on the models file. Now every screen above it has to change. The writing was fast. The review was slow. That is the real bottleneck. The old workaround You already know it. Split the work into branches yourself. git checkout -b feat/booking-models git checkout -b feat/booking-repo # branched off models git checkout -b feat/booking-state # branched off repo git checkout -b feat/booking-ui # branched off state This works for about one day. Then a reviewer asks for a change in feat/booking-models , and you rebase three branches by hand. Then it happens again. Most people give up and go back to the giant branch. What stacked pull requests change On July 30 2026 GitHub put stacked pull requests into public preview. The idea is small. A stack is an ordered series of pull requests. Each pull request targets the one below it instead of targeting main . Only the bottom one targets main . PR #4 screens and widgets -> targets PR #3 PR #3 cubits and state -> targets PR #2 PR #2 repository and api -> targets PR #1 PR #1 models and parsing -> targets main Because each pull request only contains its own layer, opening PR #3 shows you the state management diff and nothing else. Not the models. Not the widgets. The Flutter example Say I am building a booking flow. Here is the same feature, sliced. PR 1 - models and json parsing. Around 190 lines. Targets main. class Booking { final String id ; final DateTime startsAt ; final BookingStatus status ; const Booking ({ required

2026-08-02 原文 →