Mobile App Development — Advanced Guide
Master production Flutter: architecture, auth, Firebase, offline sync, performance, testing, security, and publishing.
Production Flutter Architecture
30 minMoving from tutorial apps to production apps requires a deliberate architecture. This lesson introduces the principles that keep large Flutter apps maintainable.
What you'll learn
- Understand why architecture matters
- Learn layered architecture principles
- Plan a production-ready folder structure
Concept
Production apps handle real users, real data, and real failures. Good architecture separates concerns — data, logic, and UI live in different layers. This makes the app testable, maintainable, and easier for multiple developers to work on simultaneously.
How it works
A common Flutter production structure separates the app into features, each with its own data, domain, and presentation layers. Shared code — networking, storage, utilities — lives in a core folder used by every feature.
Example
Understand it
Feature-first means each feature is self-contained. Core is shared. This scales from 5 screens to 100 without becoming a mess.
Practice
Design the folder structure for a shopping app with auth, products, cart, and checkout features.
Clean Architecture
35 minClean Architecture is a well-known approach that makes code independent of frameworks and easy to test by separating it into concentric layers.
What you'll learn
- Understand Clean Architecture layers
- Separate entities, use cases, and UI
- Apply dependency rules
Concept
Clean Architecture divides code into Entities (business objects), Use Cases (business rules), and Interface Adapters (repositories, controllers, presenters). The key rule: dependencies point inward — outer layers depend on inner layers, never the reverse.
How it works
Business logic lives in the innermost layers and knows nothing about Flutter, databases, or APIs. Outer layers (UI, frameworks) depend on inner layers through interfaces, so you can swap implementations without touching business rules.
Example
Understand it
Because business logic is pure Dart and has no Flutter imports, you can unit-test it without emulators or mock frameworks.
Practice
Identify which layer a 'Login with email' use case belongs to and what it should NOT import.
Repository Pattern
30 minThe repository pattern is the standard way to separate data access from business logic. Your code asks a repository for data without caring where it comes from.
What you'll learn
- Understand the repository abstraction
- Hide data sources behind a repository
- Swap data sources easily
Concept
A repository is a class that provides data to the rest of the app. It decides whether to fetch from the network, read from a local database, or use cached data. Business logic depends on the repository interface, not on specific data sources.
How it works
Define an abstract repository with methods like getUser(id) or getProducts(). Implement it with a class that uses HTTP, SQLite, or both. The rest of the app only knows the abstract type, so switching data sources is transparent.
Example
Understand it
The abstract class is the contract. Concrete implementations handle the 'how'. Callers only see the contract, making tests and future changes much easier.
Practice
Define an abstract ProductRepository with a method to fetch products.
Dependency Injection
30 minDependency injection (DI) supplies objects their dependencies instead of creating them internally. It makes code testable and decoupled.
What you'll learn
- Understand dependency injection
- Use constructor injection
- Wire dependencies with Riverpod/Provider
Concept
Instead of a class creating its own dependencies (new ApiClient()), those dependencies are passed in through the constructor or provided from outside. This lets you swap real implementations with fakes in tests and centralizes object creation.
How it works
Flutter apps commonly use Provider or Riverpod for DI. You declare providers for services and repositories, then widgets or use cases read them. At the composition root (main), you decide which concrete implementations to provide.
Example
Understand it
Constructor injection makes dependencies explicit — you can see exactly what a class needs just by looking at its constructor.
Practice
Refactor a class that creates its own ApiClient to accept it via constructor instead.
Advanced State Management
35 minBeyond simple counters, real apps have complex, interconnected state. This lesson covers advanced patterns for managing it predictably.
What you'll learn
- Understand complex state challenges
- Choose advanced state tools
- Structure state by domain
Concept
Complex state includes async data, multiple data sources, shared state across features, and derived state. Tools like Riverpod, Bloc, and Provider handle these with patterns like state notifiers, selectors, and event streams. The goal is predictable state changes and minimal rebuilds.
How it works
State is held in dedicated state classes. UI watches only the slices it needs using selectors. Events or methods update state and notify only relevant listeners, avoiding whole-app rebuilds.
Example
Understand it
Immutable state + copyWith makes changes predictable and easy to trace. Selectors let widgets watch only the part of state they care about.
Practice
Split a single app state class into separate domain-specific state classes.
Complex Async State
30 minReal async operations have loading, data, and error states — often all at once. This lesson shows how to model complex async state cleanly.
What you'll learn
- Model loading/data/error together
- Handle concurrent async operations
- Avoid stale data bugs
Concept
A naive approach keeps separate booleans for loading and error, which quickly becomes inconsistent. Better is a sealed class or state object that represents exactly one state at a time: Loading, Success(data), or Error(message). This makes impossible states unrepresentable.
How it works
Define a sealed class hierarchy (Dart 3 sealed classes work well). Async operations return one of these states. UI switches on the state and renders the correct view for each case.
Example
Understand it
Because the state can only be one of Loading, Success, or Error, the compiler ensures you handle every case — no more 'loading but also error' bugs.
Practice
Write a sealed class hierarchy for a Future's loading, success, and error states.
Authentication Concepts
30 minAuthentication verifies who a user is. This lesson covers the core concepts — sessions, tokens, and credentials — before implementing anything.
What you'll learn
- Understand authentication vs authorization
- Learn session and token concepts
- Know common auth flows
Concept
Authentication confirms identity (who are you?); authorization controls access (what can you do?). Modern apps commonly use token-based auth: user logs in with credentials, server returns a token (like JWT), and the app sends that token on subsequent requests. Tokens expire and can be refreshed.
How it works
Login → server validates credentials → returns access token + refresh token. App stores them securely. Each API request includes the access token. When it expires, the app uses the refresh token to get a new access token without re-login.
Example
Understand it
Tokens are safer than storing passwords because they are short-lived, revocable, and scoped. The refresh token has a longer life but is only used to obtain new access tokens.
Practice
Explain the difference between an access token and a refresh token.
Email & Password Authentication
35 minThe most common auth method is email and password. This lesson implements the login and signup flows in Flutter.
What you'll learn
- Build login and signup forms
- Call auth endpoints
- Store tokens securely
Concept
Email/password auth sends credentials to a backend that verifies them against its user database. The app collects email and password via forms, sends them over HTTPS, and handles the response — success (store token) or failure (show error). Passwords must never be stored in plain text on the client.
How it works
Collect input, validate the form, send a POST to /auth/login with JSON credentials. On success, parse the token response and store tokens in secure storage. On failure, show the server's error message. Signup follows the same pattern with a registration endpoint.
Example
Understand it
The token is the session. Store it securely (not in SharedPreferences, which is unencrypted), and send it on every authenticated request.
Practice
Write code to send a login request and handle both success and error responses.
Google / Social Authentication
35 minSocial login lets users sign in with Google, Apple, or other providers instead of creating a new password. It is faster and more secure.
What you'll learn
- Understand OAuth social login
- Add Google sign-in
- Handle social auth tokens
Concept
Social auth uses OAuth — the app redirects to the provider (Google/Apple), the user approves, and the provider returns an identity token. Your server verifies that token and creates or finds the user account. The user never shares a password with your app.
How it works
Use a package like google_sign_in. The user taps 'Sign in with Google', approves consent, and you receive an ID token and user info. Send the ID token to your backend to verify and create a session.
Example
Understand it
The identity token is a signed assertion from Google saying 'this user is X'. Your server verifies the signature rather than trusting the client blindly.
Practice
Explain why you should verify a Google ID token on the server, not just read user info on the client.
Token & Session Management
35 minGetting a token is easy; managing its lifecycle is the hard part. This lesson covers storing, refreshing, and revoking sessions properly.
What you'll learn
- Store tokens securely
- Implement token refresh
- Handle logout and session expiry
Concept
Tokens expire. A robust app stores access and refresh tokens securely, automatically refreshes the access token when it expires, redirects to login on refresh failure, and clears tokens on logout. Session state should be shared app-wide so all screens react to auth changes.
How it works
Wrap authenticated requests in an interceptor or service that checks expiry. If expired, call the refresh endpoint with the refresh token, get a new access token, and retry the original request. If refresh fails, clear tokens and send the user to login.
Example
Understand it
The refresh flow is invisible to the user when it works — they stay logged in seamlessly. Only when the refresh token itself expires do they need to re-authenticate.
Practice
Design the flow for what happens when an API call returns 401 Unauthorized.
Secure Storage
30 minSensitive data like tokens and keys must be stored securely. This lesson covers encrypted storage for Flutter apps.
What you'll learn
- Understand secure storage vs regular storage
- Store sensitive data safely
- Choose the right secure storage package
Concept
SharedPreferences and SQLite store data in plain text — readable by anyone with device access. Secure storage encrypts data using platform keychains (Keychain on iOS, Keystore on Android). Use flutter_secure_storage for tokens, API keys, and other secrets.
How it works
flutter_secure_storage writes encrypted values to the platform's secure enclave. Read and write are async. The OS handles encryption, so even rooted devices have a harder time accessing the data.
Example
Understand it
Secure storage is the right tool for secrets. Never put passwords or tokens in SharedPreferences, logs, or code — all of those leak easily.
Practice
Refactor auth token storage from SharedPreferences to secure storage.
API Security
35 minYour app talks to servers — make those conversations secure. This lesson covers HTTPS, tokens, and input validation.
What you'll learn
- Enforce HTTPS
- Send tokens safely
- Validate server responses
Concept
All API traffic must use HTTPS to encrypt data in transit. Auth tokens go in headers, never in URLs. Responses must be validated — never trust data just because it came from your server; it could be malformed or compromised.
How it works
Use HTTPS URLs exclusively. Attach tokens as Authorization headers. Validate response JSON against your models and handle unexpected data gracefully. Add certificate pinning for very high security needs.
Example
Understand it
HTTPS protects against eavesdropping. Headers keep tokens out of logs and URLs. Validation prevents crashes and injection from malformed data.
Practice
Write a centralized function that adds auth headers to every request.
Environment Variables & Secrets
30 minHardcoding API keys in source code is a security disaster. This lesson teaches how to manage environment variables and secrets properly.
What you'll learn
- Separate secrets from code
- Use environment-specific config
- Never commit secrets to git
Concept
Secrets — API keys, tokens, endpoints — must never be in source code. Use environment variables or a config file that is excluded from version control. At runtime, load the right config based on the environment (development, staging, production).
How it works
Use a package like flutter_dotenv to load keys from a .env file that is in .gitignore. Reference values through a config object. In CI/CD, provide real secrets via the pipeline's secret store, not the repo.
Example
Understand it
Secrets belong in the environment, not the repository. If a secret is committed, assume it is compromised and rotate it immediately.
Practice
List the steps to securely add an API key to a Flutter project.
Role-Based Access
30 minNot all users should see everything. Role-based access control (RBAC) restricts features based on the user's role.
What you'll learn
- Understand roles and permissions
- Implement UI-level access control
- Enforce access server-side
Concept
Users have roles — admin, editor, viewer. Each role has permissions. The app shows or hides features based on the user's role, but the real enforcement happens server-side because client-side checks can be bypassed.
How it works
On login, the server returns the user's role. The app stores it and conditionally renders admin-only features. Every protected API endpoint still checks the role server-side, so hiding a button doesn't actually prevent access if someone calls the API directly.
Example
Understand it
Client-side checks are UX convenience; server-side checks are the actual security. Never rely only on hiding UI.
Practice
Add a role check that only shows a 'Manage Users' button for admins.
Firebase Fundamentals
35 minFirebase is Google's backend-as-a-service. It gives you auth, database, storage, and more without managing servers.
What you'll learn
- Understand Firebase services
- Set up a Firebase project
- Connect Firebase to Flutter
Concept
Firebase provides ready-made backend services: Authentication, Firestore (NoSQL database), Storage (files), Cloud Functions, and more. Instead of building your own backend, you configure Firebase and use its SDKs from Flutter. It is ideal for MVPs and apps that need a backend fast.
How it works
Create a Firebase project in the console, add your app (Android/iOS), download config files (google-services.json / GoogleService-Info.plist), add them to your Flutter project, then use packages like firebase_core and cloud_firestore.
Example
Understand it
Firebase replaces your custom backend for common needs. You still write client code, but the infrastructure is managed for you.
Practice
List the first three steps to connect Firebase to a Flutter app.
Cloud Firestore
35 minFirestore is Firebase's NoSQL cloud database — flexible, real-time, and serverless. This lesson covers basic reads and writes.
What you'll learn
- Understand Firestore data model
- Read and write documents
- Listen for real-time updates
Concept
Firestore stores data as collections of documents. Each document is a set of key-value fields. It supports real-time listeners — when data changes on the server, your app updates instantly without polling. Queries filter and sort data from collections.
How it works
Reference a collection with FirebaseFirestore.instance.collection('users'). Add documents, read them, and listen with snapshots(). Query with where() and orderBy() for filtered results.
Example
Understand it
The real-time listener is Firestore's killer feature — no polling, no refresh buttons; data just appears when it changes.
Practice
Write code to add a document and listen to collection changes.
Firebase Storage
30 minFirebase Storage stores files — images, videos, PDFs — in the cloud and gives you URLs to access them.
What you'll learn
- Upload files to Firebase Storage
- Get download URLs
- Handle file metadata
Concept
Firebase Storage is object storage for large binary files. You upload from the device, get a download URL, and store that URL in Firestore or use it directly in Image widgets. Security rules control who can read and write files.
How it works
Reference a storage bucket, upload a file from a local path, and get a download URL after upload completes. Use the URL to display the file or store it in a database.
Example
Understand it
Storage handles the binary data, while Firestore handles the metadata (like the URL). This separation keeps the database small and fast.
Practice
Write the flow to upload a photo and retrieve its download URL.
Push Notifications
35 minPush notifications re-engage users even when the app is closed. This lesson covers setting up and sending notifications.
What you'll learn
- Understand push notification flow
- Set up FCM in Flutter
- Handle notification taps
Concept
Push notifications flow from your server through a push service (like Firebase Cloud Messaging) to the device. The app registers with FCM, receives a device token, sends that token to your backend, and your backend uses it to send messages. Notifications can be data-only or display messages.
How it works
Configure FCM, initialize it in Flutter, request permission, get the device token, and listen for incoming messages. Handle foreground, background, and terminated states. When a user taps a notification, route them to the relevant screen.
Example
Understand it
The device token uniquely identifies this app installation. Your backend sends a message to that token, and FCM delivers it to the right device.
Practice
Explain why device tokens should be sent to the backend rather than hardcoded.
Deep Links
30 minDeep links open a specific screen inside your app from a URL — from a browser, another app, or a notification.
What you'll learn
- Understand deep links
- Configure deep links in Flutter
- Route users to specific screens
Concept
A deep link is a URL like myapp://product/123 that opens your app directly on the product screen instead of the home screen. Flutter uses a router (like go_router) and platform configuration to map URLs to screens. Deep links work from browser links, notifications, and share messages.
How it works
Define routes with URL patterns in go_router. Configure Android intent filters and iOS associated domains. When the app opens via a URL, the router parses it and navigates to the matching screen with any parameters.
Example
Understand it
The router maps URL paths to screens. Parameters in the URL (like :id) become arguments to the screen, enabling precise navigation from outside the app.
Practice
Define a deep link route for /user/:id that opens a UserScreen.
Background Tasks
35 minSome work must continue when the app is not in the foreground — syncing, downloads, and timers. This lesson covers background execution.
What you'll learn
- Understand background execution limits
- Schedule background work
- Handle platform restrictions
Concept
Mobile OSes heavily restrict background work to save battery. iOS and Android allow short background execution, background fetch, and push-triggered work. Use packages like workmanager for scheduled tasks and understand that long-running background work is limited by the platform.
How it works
workmanager schedules tasks that run periodically or after a delay. The task is a callback that runs in a background isolate. Be aware: exact timing is not guaranteed, and both OSes may defer work when battery is low.
Example
Understand it
Background tasks are best-effort, not guaranteed. Design your app to sync gracefully whenever it gets a chance, rather than relying on exact scheduling.
Practice
Explain why background tasks should not rely on exact timing.
Offline-First Applications
35 minOffline-first apps work with no internet connection, syncing later when connectivity returns. This is the gold standard for reliability.
What you'll learn
- Understand offline-first design
- Cache data locally
- Sync when online
Concept
An offline-first app treats the local database as the source of truth. Reads always come from local storage, writes are stored locally first, and a background sync pushes changes to the server when connectivity is available. This makes the app instant and reliable even on flaky networks.
How it works
Store data in SQLite or a local cache. On startup, load from local storage for instant UI. Periodically fetch remote changes and merge them. Queue user actions and replay them when online.
Example
Understand it
The local database is the source of truth. The server is just a sync target, which means the app never blocks on network calls.
Practice
Design the data flow for a notes app that works offline and syncs later.
Data Synchronization
35 minOffline apps need a sync strategy to merge local and remote changes without losing data. This lesson covers conflict handling.
What you'll learn
- Understand sync challenges
- Handle conflicts
- Merge local and remote changes
Concept
When both the local device and the server have changed the same record, you have a conflict. Sync strategies include last-write-wins (simplest), version vectors, and operation-based transforms. The right choice depends on how likely conflicts are and how critical data loss is.
How it works
Track timestamps or version numbers on records. During sync, compare local and remote versions. If one is newer, take it. If both changed (conflict), apply a merge rule — for most apps, last-write-wins with timestamps is sufficient.
Example
Understand it
last-write-wins is simple but can silently drop a user's edit during a conflict. For critical data, use more sophisticated merge strategies.
Practice
Implement a last-write-wins sync check between two timestamps.
Caching Strategies
30 minCaching stores frequently used data for faster access. A good caching strategy balances freshness with speed.
What you'll learn
- Understand caching types
- Implement in-memory caching
- Use time-based cache invalidation
Concept
Caching keeps data in fast storage (memory or disk) to avoid slow network or database reads. Cache strategies include cache-aside (check cache, fetch on miss), write-through (write to cache and source together), and time-based expiration (TTL). Choose based on how often data changes.
How it works
For cache-aside: check if the cached value exists and is fresh. If yes, return it. If no or stale, fetch from the source, store in the cache, and return. Time-based invalidation marks entries stale after a set duration.
Example
Understand it
TTL (time to live) is the simplest invalidation — data is fresh for N minutes, then refetched. It is perfect for data that doesn't change second-to-second.
Practice
Write a cache check that returns cached data if not expired, else fetches fresh.
Performance Optimization
35 minA smooth, fast app keeps users happy. This lesson covers profiling and the most impactful performance improvements.
What you'll learn
- Profile Flutter apps
- Identify performance bottlenecks
- Apply common optimizations
Concept
Performance issues usually show as jank (dropped frames) — the UI stutters. Use Flutter DevTools and the performance overlay to find which frames are slow. Common causes are unnecessary rebuilds, heavy build methods, and rendering large widgets inefficiently.
How it works
Run the app with the performance overlay. Find frames that exceed 16ms (60fps budget). Use DevTools to inspect the widget tree and isolate slow builds. Apply fixes like const widgets, ListView.builder, and splitting large widgets.
Example
Understand it
Each frame must complete in ~16ms. Anything that rebuilds more widgets than necessary or does heavy work in build() risks dropping frames.
Practice
List three common causes of jank in Flutter apps.
Flutter Rendering & Rebuilds
35 minUnderstanding how Flutter renders and rebuilds widgets is the key to writing efficient UIs.
What you'll learn
- Understand the widget/render tree
- Control rebuilds
- Use const and selectors effectively
Concept
Flutter maintains a widget tree, an element tree, and a render tree. When state changes, affected widgets rebuild. A rebuild re-runs the build method but only re-renders what actually changed visually. Const widgets never rebuild, and selectors limit rebuilds to widgets that depend on changed data.
How it works
setState marks a widget dirty, scheduling a rebuild. Flutter diffs the old and new widget trees and updates only the parts that differ. Const widgets are identical each build, so Flutter skips them. Selectors watch specific state slices and rebuild only when that slice changes.
Example
Understand it
Rebuild ≠ re-render. A rebuild is cheap if the resulting widget is mostly const or unchanged. Selectors make rebuilds even more targeted.
Practice
Identify which widgets rebuild when a single counter changes in a large screen.
Memory & Resource Management
30 minMemory leaks and unmanaged resources crash apps over time. This lesson covers finding and fixing them.
What you'll learn
- Understand memory leaks
- Dispose controllers and listeners
- Profile memory usage
Concept
Memory leaks happen when objects are no longer needed but still referenced, so garbage collection can't free them. In Flutter, the most common cause is forgetting to dispose controllers, streams, and listeners. Over time, leaks grow and the app slows or crashes.
How it works
Controllers like TextEditingController, AnimationController, and StreamSubscription must be disposed. Override dispose() in State to release them. DevTools memory view shows memory growth over time, helping spot leaks.
Example
Understand it
Dispose releases native resources and removes listeners. Every controller you create should have a matching dispose call.
Practice
Add proper dispose calls to a screen with a TextEditingController and a StreamSubscription.
Animations & Motion
35 minAnimations bring apps to life and guide user attention. This lesson covers Flutter's animation system.
What you'll learn
- Understand implicit vs explicit animations
- Animate widgets smoothly
- Build custom animations
Concept
Implicit animations (AnimatedContainer, AnimatedOpacity) animate a property change automatically. Explicit animations use AnimationController for full control over timing and curves. Most everyday animations can be built with implicit widgets; use explicit when you need chained or physics-based motion.
How it works
Implicit animations watch a property and smoothly transition when it changes. Explicit animations need an AnimationController with a duration and a listener that rebuilds on each tick. Curves (like Curves.easeInOut) make motion feel natural.
Example
Understand it
Implicit animations handle the animation lifecycle for you — change the value, get a smooth transition. Explicit controllers give you frame-by-frame control when needed.
Practice
Animate a box expanding when tapped using an implicit animation.
Advanced Custom UI
35 minSometimes built-in widgets aren't enough. This lesson covers custom painting and advanced widget composition.
What you'll learn
- Use CustomPainter
- Build custom shapes and charts
- Compose complex UIs
Concept
CustomPainter gives you a canvas to draw anything — custom charts, shapes, and effects. It works alongside normal widgets, so you can build custom visual elements and wrap them in standard layout widgets. This is how apps create unique, branded interfaces.
How it works
Extend CustomPainter and override paint(canvas, size) to draw with Canvas API. Override shouldRepaint to control when the painter redraws. Wrap it in CustomPaint widget and use standard layout around it.
Example
Understand it
CustomPainter draws directly on a canvas. shouldRepaint returning false means the drawing is static; true means it needs redrawing when properties change.
Practice
Draw a simple diagonal line using CustomPainter.
Accessibility
30 minAccessible apps work for everyone, including users with disabilities. This lesson covers Flutter's accessibility features.
What you'll learn
- Understand accessibility principles
- Add semantic labels
- Support screen readers
Concept
Accessibility ensures users with visual, motor, or cognitive impairments can use your app. Flutter provides semantics — descriptions of widgets for screen readers — and supports large text, high contrast, and focus navigation. Accessible design benefits all users.
How it works
Add semantic labels to images and buttons, use proper contrast ratios, support text scaling, and ensure tappable targets are large enough. Flutter's Semantics widget and tooltips make content readable by screen readers.
Example
Understand it
Screen readers read semantic labels aloud. Without them, images and icon-only buttons are invisible to visually impaired users.
Practice
Add a semantic label to an icon-only button.
Internationalization & Localization
35 minGlobal apps speak many languages. This lesson covers translating your app for different locales.
What you'll learn
- Understand i18n and l10n
- Set up localization
- Support multiple languages
Concept
Internationalization (i18n) is designing the app to support many languages. Localization (l10n) is the actual translation into specific locales. Flutter's gen_l10n tool generates localization classes from ARB files, letting you swap text based on the device's language.
How it works
Create ARB files for each locale (app_en.arb, app_es.arb). Configure l10n in pubspec. Generated classes expose translated strings. Set the app's locale based on the device, and wrap with MaterialApp's localizationsDelegates.
Example
Understand it
ARB files hold translations. The code references a key, and the framework picks the right language based on locale.
Practice
Add a Spanish translation for a greeting string.
Unit Testing
30 minUnit tests verify individual functions and classes work correctly in isolation. They are the foundation of reliable software.
What you'll learn
- Write unit tests in Dart
- Test pure business logic
- Use mocks for dependencies
Concept
A unit test exercises a single function or class with known inputs and asserts the output. Pure Dart code (no Flutter, no network) is easy to test. Mock dependencies like repositories to isolate the logic being tested. Run tests with flutter test.
How it works
Use the test package. Write test('description', () { ... expect(actual, expected); }). For code with dependencies, inject mock objects that return fixed values.
Example
Understand it
expect() compares actual to expected. A test passes if all expectations match; any mismatch fails the test with a clear message.
Practice
Write a unit test for a function that capitalizes a string.
Widget Testing
35 minWidget tests verify that UI components render and interact correctly without a full device.
What you'll learn
- Write widget tests
- Find and interact with widgets
- Assert UI behavior
Concept
Widget tests pump a widget into a test environment and verify its output. You can tap buttons, enter text, and assert that the expected UI appears. They run faster than full integration tests and catch most UI logic errors.
How it works
Use testWidgets(). Pump the widget, use find.text/find.byType to locate elements, call tester.tap() and tester.enterText() for interactions, and expect() to verify results. pump() advances a frame after changes.
Example
Understand it
Widget tests render the widget tree in a fake environment. find locates widgets, tap simulates user interaction, pump rebuilds the tree, and expect verifies the result.
Practice
Write a widget test that verifies a button label is visible.
Integration Testing
35 minIntegration tests run the full app on a real device or emulator, verifying that all parts work together end-to-end.
What you'll learn
- Understand integration tests
- Write end-to-end test flows
- Run tests on devices
Concept
Integration tests exercise the real app from launch through user flows. Unlike unit/widget tests, they run on actual devices and can use real plugins and network. They are slower but catch issues that only appear when everything is connected.
How it works
Use integration_test package. Write testWidgets with real app startup (app.main()), perform multi-step user flows, and assert results. Run with flutter test integration_test or on a device.
Example
Understand it
Integration tests verify real workflows — login, checkout, navigation — using the full app stack with real plugins and backend calls.
Practice
Describe when an integration test is more valuable than a unit test.
Debugging Production Issues
30 minProduction bugs are hard because you can't attach a debugger to a user's phone. This lesson covers remote debugging strategies.
What you'll learn
- Understand production debugging challenges
- Use logging strategically
- Reproduce issues locally
Concept
In production, you rely on logs, crash reports, and user feedback instead of a live debugger. Strategic logging captures the state when errors occur. Crash reporting tools collect stack traces. Reproducing the issue locally, even with mocked data, is often the fastest path to a fix.
How it works
Add meaningful logs at key points — request/response, state changes, errors. Use a crash reporting tool like Sentry or Crashlytics to capture crashes with context. When a user reports a bug, reproduce it with the logged data.
Example
Understand it
Logs tell you what happened before a crash. Crash reports give the stack trace. Together they pinpoint the cause without needing the user's device.
Practice
List the two most important things to log before an API call and after a crash.
Crash Reporting & Monitoring
30 minYou can't fix crashes you don't know about. Crash reporting tools capture errors automatically and alert you.
What you'll learn
- Set up crash reporting
- Understand crash reports
- Monitor app health
Concept
Crash reporting tools (Crashlytics, Sentry) automatically collect uncaught errors, stack traces, device info, and user context. You get a dashboard of crashes grouped by type, showing which are most common and most severe. This turns invisible production failures into actionable data.
How it works
Initialize the crash reporter at app startup. Uncaught errors are captured automatically. Add custom context (user ID, screen) to make reports more useful. Monitor the dashboard and fix the highest-impact crashes first.
Example
Understand it
Crash reports group identical crashes, so you see 'this crash affected 5,000 users' and can prioritize accordingly.
Practice
Set a user identifier in Crashlytics so crashes can be traced to specific users.
App Security Best Practices
35 minSecurity must be a first-class concern, not an afterthought. This lesson summarizes the most critical mobile security practices.
What you'll learn
- Protect user data
- Secure network communication
- Prevent common vulnerabilities
Concept
Mobile security spans secure storage, encrypted communication, input validation, least-privilege permissions, and protecting secrets. Key principles: never store plaintext secrets, always use HTTPS, validate all input, request only needed permissions, and treat the client as untrusted.
How it works
Apply layers: secure storage for secrets, HTTPS for transit, certificate pinning for high security, server-side validation for all input, and permission minimization. Regular security reviews and dependency updates close known vulnerabilities.
Example
Understand it
Security is layered — no single measure is enough. Each layer covers a different attack vector, and together they make exploitation much harder.
Practice
List three security practices every production app should follow.
CI/CD for Flutter
35 minCI/CD automates building, testing, and deploying your app. It catches errors early and speeds up releases.
What you'll learn
- Understand CI/CD concepts
- Set up automated builds and tests
- Deploy automatically
Concept
CI (Continuous Integration) automatically runs tests and builds whenever code is pushed. CD (Continuous Delivery/Deployment) automates releasing those builds. Tools like GitHub Actions, Codemagic, and GitLab CI can build, test, sign, and publish Flutter apps automatically.
How it works
Configure a pipeline (YAML file) that triggers on git push. The pipeline runs lint, unit tests, widget tests, then builds a release APK/IPA. On tags or main branch merges, it can sign and upload to app stores or internal test tracks.
Example
Understand it
The pipeline catches broken code immediately after push, before it can affect users or other developers. This is the backbone of reliable releases.
Practice
Write the core steps of a CI pipeline for a Flutter app.
Android Build, Signing & Release
35 minShipping an Android app requires signing a release build with a secure key. This lesson walks through the process.
What you'll learn
- Build a release APK/AAB
- Sign with a keystore
- Prepare for Play Store upload
Concept
Android requires all release apps to be digitally signed. You generate a keystore with a private key, configure Flutter to use it, and build a release App Bundle (.aab) — the format Play Store requires. Protect the keystore and password, because losing them means you can't update the app.
How it works
Generate a keystore with keytool, create key.properties with credentials, reference it in build.gradle, then run flutter build appbundle. The signed .aab is ready to upload to Play Console.
Example
Understand it
The keystore is your app's identity. Lose it, and you cannot publish updates to the same app on Play Store.
Practice
List the files and credentials that must be kept out of git for Android signing.
iOS Build, Signing & Release
35 minPublishing to the App Store requires Apple Developer credentials, certificates, and provisioning profiles. This lesson covers the flow.
What you'll learn
- Understand Apple signing
- Configure Xcode signing
- Build and upload to TestFlight
Concept
iOS apps must be signed with an Apple certificate and provisioning profile. You need an Apple Developer account, create an app identifier, and configure signing in Xcode. Flutter builds an IPA or uses Xcode to archive and upload to App Store Connect for TestFlight and review.
How it works
Create an App ID and distribution certificate in the Apple Developer portal. In Xcode, enable automatic signing with your team. Archive the app, then upload to App Store Connect. TestFlight is used for beta testing before public release.
Example
Understand it
Apple's signing is more complex than Android's — multiple certificates and profiles, all tied to your developer account. Automatic signing in Xcode simplifies most of it.
Practice
What are the two most important things needed to sign an iOS app?
App Store / Play Store Publishing + Capstone
45 minThe final step is publishing your app to the world. This lesson covers store submission and wraps the entire path with a capstone.
What you'll learn
- Prepare store listing
- Submit for review
- Complete the full capstone app
Concept
Publishing involves creating store listings (title, description, screenshots, icon), setting content ratings, and submitting for review. Both stores have approval processes — Apple's is stricter. The capstone brings everything together: a complete, production-ready app that demonstrates all 90 lessons.
How it works
For Play Store: create listing, upload .aab, set rating, submit. For App Store: create listing, upload via Xcode/TestFlight, set metadata, submit for review. Respond to review feedback if needed. The capstone should be a real app — auth, data, offline sync, tested, and published.
Example
Understand it
The store listing is marketing — clear descriptions, good screenshots, and honest privacy info improve installs and approval chances.
Practice
List the key items needed in a store listing.
You have completed all 40 advanced lessons.
You've finished the complete Flutter path — from zero to production.
Continue to Practice Hub