diff --git a/task_manager_app/lib/home_page.dart b/task_manager_app/lib/home_page.dart index 003a2d7..7d87138 100644 --- a/task_manager_app/lib/home_page.dart +++ b/task_manager_app/lib/home_page.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:task_manager_app/model/todo_model.dart'; +import 'package:task_manager_app/provider/todo_provider.dart'; import 'student_registration_screen.dart'; class HomePage extends StatelessWidget { @@ -6,51 +9,25 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Task Manager'), - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.check_circle_outline, - size: 64, - color: Theme.of(context).colorScheme.primary, - ), - const SizedBox(height: 16), - Text( - 'Welcome to Group 1', - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 8), - Text( - 'Week 2 – Your first Flutter app', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - - const SizedBox(height: 30), + final provider = context.watch(); - // Student Registration Button - ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - const StudentRegistrationScreen(), - ), - ); - }, - child: const Text('Student Registration form'), - ), - ], + return Scaffold( + appBar: AppBar( + title: const Text('Task Manager'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, ), - ), - ); + body: provider.isLoading + ? const Center( + child: CircularProgressIndicator(), + ) + : ListView.builder( + itemCount: provider.todos.length, + itemBuilder: (context, index) { + final Todo todo = provider.todos[index]; + return ListTile( + title: Text(todo.title), + ); + }, + )); } } diff --git a/task_manager_app/lib/main.dart b/task_manager_app/lib/main.dart index 1659163..5474182 100644 --- a/task_manager_app/lib/main.dart +++ b/task_manager_app/lib/main.dart @@ -1,9 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:task_manager_app/provider/todo_provider.dart'; import 'student_registration_screen.dart'; import 'home_page.dart'; void main() { - runApp(const TaskManagerApp()); + runApp(ChangeNotifierProvider( + create: (_) => TodoProvider()..fetchAllTodos(), + child: const TaskManagerApp(), + )); } class TaskManagerApp extends StatelessWidget { diff --git a/task_manager_app/lib/model/todo_model.dart b/task_manager_app/lib/model/todo_model.dart new file mode 100644 index 0000000..a006b5b --- /dev/null +++ b/task_manager_app/lib/model/todo_model.dart @@ -0,0 +1,32 @@ +class Todo { + final int userId; + final int id; + final String title; + final bool completed; + + Todo({ + required this.userId, + required this.id, + required this.title, + required this.completed, + }); + + + factory Todo.fromJson(Map json) { + return Todo( + userId: json['userId'] as int, + id: json['id'] as int, + title: json['title'] as String, + completed: json['completed'] as bool, + ); + } + + Map toJson() { + return { + 'userId': userId, + 'id': id, + 'title': title, + 'completed': completed, + }; + } +} diff --git a/task_manager_app/lib/provider/todo_provider.dart b/task_manager_app/lib/provider/todo_provider.dart new file mode 100644 index 0000000..2485e7f --- /dev/null +++ b/task_manager_app/lib/provider/todo_provider.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:task_manager_app/model/todo_model.dart'; +import 'package:task_manager_app/service/api_service.dart'; + +class TodoProvider extends ChangeNotifier { + bool isLoading = false; + String errorMessage = ""; + List todos = []; + + Future> fetchAllTodos() async { + isLoading = true; + try { + todos = await ApiService().fetchAllTodos(); + return todos; + } catch (e) { + errorMessage = e.toString(); + return []; + } finally { + isLoading = false; + notifyListeners(); + } + } +} diff --git a/task_manager_app/lib/service/api_service.dart b/task_manager_app/lib/service/api_service.dart new file mode 100644 index 0000000..e75d7d1 --- /dev/null +++ b/task_manager_app/lib/service/api_service.dart @@ -0,0 +1,19 @@ +import 'package:dio/dio.dart'; +import 'package:task_manager_app/model/todo_model.dart'; + +class ApiService { + final Dio dio = + Dio(BaseOptions(baseUrl: "https://jsonplaceholder.typicode.com/")); + + Future> fetchAllTodos() async { + final response = await dio.get("/todos?_limit=10"); + if (response.statusCode == 200) { + final List data = response.data; + return data + .map((item) => Todo.fromJson(item as Map)) + .toList(); + } else { + throw Exception("Failed to load todos"); + } + } +} diff --git a/task_manager_app/pubspec.lock b/task_manager_app/pubspec.lock index 9ce301c..4d9a7c0 100644 --- a/task_manager_app/pubspec.lock +++ b/task_manager_app/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.0" clock: dependency: transitive description: @@ -49,6 +49,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dio: + dependency: "direct main" + description: + name: dio + sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c + url: "https://pub.dev" + source: hosted + version: "5.9.2" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" + url: "https://pub.dev" + source: hosted + version: "2.1.2" fake_async: dependency: transitive description: @@ -75,30 +91,46 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "11.0.2" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.10" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.1" lints: dependency: transitive description: @@ -111,26 +143,42 @@ packages: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.13.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.0.0" path: dependency: transitive description: @@ -139,6 +187,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" sky_engine: dependency: transitive description: flutter @@ -148,10 +204,10 @@ packages: dependency: transitive description: name: source_span - sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.2" + version: "1.10.1" stack_trace: dependency: transitive description: @@ -188,26 +244,42 @@ packages: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "1.4.0" vector_math: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.4" vm_service: dependency: transitive description: name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "15.0.2" + version: "1.1.1" sdks: - dart: ">=3.9.0-0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/task_manager_app/pubspec.yaml b/task_manager_app/pubspec.yaml index e03cd44..7f504d9 100644 --- a/task_manager_app/pubspec.yaml +++ b/task_manager_app/pubspec.yaml @@ -10,6 +10,9 @@ dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.6 + http: ^1.6.0 + dio: ^5.9.2 + provider: ^6.1.5+1 dev_dependencies: flutter_test: diff --git a/week-guides/week3/README.md b/week-guides/week3/README.md new file mode 100644 index 0000000..51adf8e --- /dev/null +++ b/week-guides/week3/README.md @@ -0,0 +1,88 @@ +# Week 3 — Async, APIs & State (Flutter) + +Welcome to Week 3. You will learn how apps **wait for data** without freezing, **talk to the internet**, and **share state** across screens without messy code. + +--- + +## Learning objectives + +By the end of this week you should be able to: + +- Explain why some code runs **later** (async) and how `Future` and `async`/`await` help. +- Call a **REST API** with `http`, handle **loading** and **errors**, and show data in the UI. +- Tell **local state** (`setState`) apart from **global state** (shared across widgets). +- Recognize **prop drilling** and know when to use **Provider** instead. +- Use **ChangeNotifier** + **Provider** to update the UI when data changes. + +--- + +## Topics covered + +| Topic | What it is (short) | +|--------|---------------------| +| **Async programming** | `Future`, `async`/`await` — code that finishes later without blocking the UI thread. | +| **API integration** | HTTP `GET`/`POST`, JSON, turning responses into Dart objects. | +| **Local vs global state** | One widget’s data vs data many widgets need. | +| **Prop drilling** | Passing the same data through many parent widgets — gets painful fast. | +| **Provider** | A simple way to put state “above” the tree and listen to changes. | + +--- + +## Suggested learning order + +1. **async-await** (`theory/async-await.md`) — understand `Future` and `async`/`await` first. +2. **API** (`theory/api-and-async.md`) — then add real network calls. +3. **State** (`theory/state-management.md`) — local state and `setState`. +4. **Prop drilling** (`theory/prop-drilling.md`) — why passing data down many layers hurts. +5. **Provider** (`theory/provider.md`) — share state without passing props everywhere. +6. **Deep Provider guide** (new files below) — practical step-by-step use. + +**Diagrams** in `diagrams/` give a visual overview. **Sample code** in `sample-code/` ties it together. + +--- + +## Folder map + +| Folder | Purpose | +|--------|---------| +| `theory/` | Short lessons (read in order above). | +| `diagrams/` | Mermaid diagrams — open in GitHub or an editor that renders Mermaid. | +| `sample-code/` | Example files to copy into your app (see `sample-code/README.md`). | +| `resources/` | Free public APIs to practice with. | +| `group-activity-1/` | Core lab (55 min): **same tasks for all groups**, **different API per group** — see `instructor-guide.md`. | +| `group-activity-2/` | Optional advanced lab (45–60 min): retry, latency, cache, filter, UI polish — **same API as group number in Activity 1**. | + +--- + +## How to run the project (with sample code) + +1. Open your Flutter project (e.g. `task_manager_app/`). +2. Add dependencies in `pubspec.yaml` (see `sample-code/README.md` for exact lines). +3. Copy the `.dart` files from `sample-code/` into `lib/` (or a subfolder like `lib/week3/`). +4. Fix import paths if you use subfolders. +5. Register your `TodoProvider` in `main.dart` with `ChangeNotifierProvider` (see `theory/provider.md` and `sample-code/example_screen.dart`). +6. Run: + +```bash +cd task_manager_app +flutter pub get +flutter run +``` + +--- + +## Quick links + +- [Async & await](theory/async-await.md) +- [APIs & async](theory/api-and-async.md) +- [State management](theory/state-management.md) +- [Prop drilling](theory/prop-drilling.md) +- [Provider](theory/provider.md) +- [Provider introduction](theory/provider-introduction.md) +- [Create provider](theory/provider-create.md) +- [Consume provider in UI](theory/provider-consume.md) +- [Update provider state](theory/provider-update.md) +- [Provider best practices](theory/provider-best-practices.md) +- [Public APIs](resources/public-apis.md) + +Good luck — build small, test often, read error messages slowly. diff --git a/week-guides/week3/diagrams/async-flow.md b/week-guides/week3/diagrams/async-flow.md new file mode 100644 index 0000000..c2a2c4c --- /dev/null +++ b/week-guides/week3/diagrams/async-flow.md @@ -0,0 +1,13 @@ +# Diagram: Async flow + +This shows what happens when the user triggers something that loads data **asynchronously**. + +```mermaid +flowchart LR + A[User action] --> B[Start async call] + B --> C[Waiting... UI stays responsive] + C --> D[Response arrives] + D --> E[Update state / UI] +``` + +**In words:** Tap → start request → app can still animate and scroll → data arrives → `setState` or Provider → new UI. diff --git a/week-guides/week3/diagrams/data-flow.md b/week-guides/week3/diagrams/data-flow.md new file mode 100644 index 0000000..015e678 --- /dev/null +++ b/week-guides/week3/diagrams/data-flow.md @@ -0,0 +1,19 @@ +# Diagram: Data flow (API → UI) + +Typical layers for a small app using Provider: + +```mermaid +flowchart LR + API[Remote API / JSON] + S[Service / api_service] + P[Provider / ChangeNotifier] + U[Widgets / UI] + + API --> S + S --> P + P --> U +``` + +- **Service** — `http.get`, parse JSON, maybe map errors. +- **Provider** — holds lists, loading flags, error messages; calls `notifyListeners()`. +- **UI** — `watch`es the provider and shows lists, spinners, or error text. diff --git a/week-guides/week3/diagrams/provider-flow.md b/week-guides/week3/diagrams/provider-flow.md new file mode 100644 index 0000000..67b6f0d --- /dev/null +++ b/week-guides/week3/diagrams/provider-flow.md @@ -0,0 +1,17 @@ +# Diagram: Provider state flow + +```mermaid +flowchart LR + A[User Action] --> B[Provider Method] + B --> C[State Update] + C --> D[notifyListeners] + D --> E[UI Rebuild] +``` + +Simple example: + +- User taps **Refresh** +- `TodoProvider.fetchTodos()` runs +- `loading`, `todos`, or `error` changes +- `notifyListeners()` is called +- UI updates automatically diff --git a/week-guides/week3/diagrams/state-flow.md b/week-guides/week3/diagrams/state-flow.md new file mode 100644 index 0000000..b878162 --- /dev/null +++ b/week-guides/week3/diagrams/state-flow.md @@ -0,0 +1,13 @@ +# Diagram: State flow (user → UI) + +```mermaid +flowchart LR + U[User action] + S[State changes] + R[UI rebuild] + + U --> S + S --> R +``` + +**Example:** User taps “Refresh” → provider clears list, sets `loading = true`, fetches API → sets todos + `loading = false` → `notifyListeners()` → widgets that `watch` the provider **rebuild** and show the new list. diff --git a/week-guides/week3/group-activity-1/group-1.md b/week-guides/week3/group-activity-1/group-1.md new file mode 100644 index 0000000..26d1224 --- /dev/null +++ b/week-guides/week3/group-activity-1/group-1.md @@ -0,0 +1,47 @@ +# Group 1 — Activity 1 (core) + +**Branch:** `week3-a1-group-1/jsonplaceholder-todos` + +--- + +## Your API (only yours) + +**Endpoint:** `https://jsonplaceholder.typicode.com/todos` +**Tip:** Add `?_limit=20` to keep the response small. + +**Expected JSON:** An **array** of objects. Each item usually has: + +| Field | Type | Notes | +|--------|------|--------| +| `id` | int | | +| `userId` | int | | +| `title` | String | Good for list text | +| `completed` | bool | Optional: show icon or strikethrough | + +--- + +## What everyone must implement (same for all groups) + +1. **Fetch** — `http.get`, `async`/`await`, parse JSON (`dart:convert`). +2. **Display** — Show items in a `ListView` (title at minimum). +3. **Loading** — Spinner or text while waiting. +4. **Error** — User-visible message if the request fails (not only `print`). +5. **Provider** — `ChangeNotifier` holding list + loading + error; `ChangeNotifierProvider` in `main.dart`. +6. **Refresh** — Button or `RefreshIndicator` to load again. +7. **Empty state** — If the list is empty after a successful parse, show a short message (e.g. “No items”). +8. **(Optional)** Print or show **response time** in ms (see `sample-code/performance_logger.dart`). + +--- + +## Steps + +1. `git checkout main && git pull` → create branch `week3-a1-group-1/jsonplaceholder-todos` +2. Add `http` and `provider` to `pubspec.yaml`, run `flutter pub get` +3. Build fetch → UI first; then move state into a Provider +4. Commit, push, open PR as your instructor directs + +--- + +## Help + +- Week 3: `theory/api-and-async.md`, `theory/provider.md`, `sample-code/` diff --git a/week-guides/week3/group-activity-1/group-10.md b/week-guides/week3/group-activity-1/group-10.md new file mode 100644 index 0000000..18fccfe --- /dev/null +++ b/week-guides/week3/group-activity-1/group-10.md @@ -0,0 +1,49 @@ +# Group 10 — Activity 1 (core) + +**Branch:** `week3-a1-group-10/dummyjson-carts` + +--- + +## Your API (only yours) + +**Endpoint:** `https://dummyjson.com/carts` +**Tip:** Response includes **`carts`** array (and `total`, `skip`, `limit`). + +**Expected JSON:** + +| Part | Notes | +|------|--------| +| `carts` | List of cart objects | +| Each cart | Often `id`, `products` (list), `total`, `discountedTotal` | + +**Display ideas:** + +| Approach | Notes | +|----------|--------| +| Show cart **`id`** + **number of products** (`products.length`) | Simplest | +| Or first product title | Requires nested access | + +--- + +## What everyone must implement (same for all groups) + +1. **Fetch** → parse **`carts`** list. +2. **Display** — one line per cart (e.g. `Cart #id — N items`). +3. **Loading** / **Error** / **Empty state** +4. **Provider** +5. **Refresh** +6. **(Optional)** Response time + +--- + +## Steps + +1. Branch: `week3-a1-group-10/dummyjson-carts` +2. Nested `products`: use `cart['products'] as List?` and `.length`. +3. Commit, push, PR + +--- + +## Help + +- This JSON is **nested** — go slowly; print `jsonDecode` shape if stuck. diff --git a/week-guides/week3/group-activity-1/group-2.md b/week-guides/week3/group-activity-1/group-2.md new file mode 100644 index 0000000..589bf59 --- /dev/null +++ b/week-guides/week3/group-activity-1/group-2.md @@ -0,0 +1,46 @@ +# Group 2 — Activity 1 (core) + +**Branch:** `week3-a1-group-2/jsonplaceholder-users` + +--- + +## Your API (only yours) + +**Endpoint:** `https://jsonplaceholder.typicode.com/users` +**Tip:** Response is a **JSON array** (no `data` wrapper). + +**Expected JSON:** Each object often includes: + +| Field | Type | Notes | +|--------|------|--------| +| `id` | int | | +| `name` | String | Good for main line | +| `username` | String | Optional subtitle | +| `email` | String | Optional subtitle | + +--- + +## What everyone must implement (same for all groups) + +1. **Fetch** — `http.get`, `async`/`await`, parse JSON. +2. **Display** — `ListView` (e.g. **name** or **name + email**). +3. **Loading** — While waiting. +4. **Error** — User-visible message on failure. +5. **Provider** — `ChangeNotifier` + `ChangeNotifierProvider`. +6. **Refresh** — Reload from network. +7. **Empty state** — Message if list is empty. +8. **(Optional)** Response time in ms. + +--- + +## Steps + +1. Branch: `week3-a1-group-2/jsonplaceholder-users` +2. Add `http`, `provider`, implement checklist above +3. Commit, push, PR + +--- + +## Help + +- `theory/`, `sample-code/`, `resources/public-apis.md` diff --git a/week-guides/week3/group-activity-1/group-3.md b/week-guides/week3/group-activity-1/group-3.md new file mode 100644 index 0000000..d4fb0b5 --- /dev/null +++ b/week-guides/week3/group-activity-1/group-3.md @@ -0,0 +1,50 @@ +# Group 3 — Activity 1 (core) + +**Branch:** `week3-a1-group-3/dummyjson-products` + +--- + +## Your API (only yours) + +**Endpoint:** `https://dummyjson.com/products` +**Tip:** Response is an **object** with a **`products`** array (not a bare array at the top level). + +**Expected JSON (shape):** + +| Part | Notes | +|------|--------| +| `products` | List of product objects | +| Each product | Often `id`, `title`, `price`, `thumbnail` (optional) | + +**Example fields per product:** + +| Field | Type | Notes | +|--------|------|--------| +| `id` | int | | +| `title` | String | List title | +| `price` | num | Optional in subtitle | + +--- + +## What everyone must implement (same for all groups) + +1. **Fetch** — `http.get` → `jsonDecode` → read **`response['products']`** as `List`. +2. **Display** — `ListView` of product **title** (and optional price). +3. **Loading** / **Error** / **Empty state** +4. **Provider** for list + flags +5. **Refresh** +6. **(Optional)** Response time + +--- + +## Steps + +1. Branch: `week3-a1-group-3/dummyjson-products` +2. Parse the **wrapper** object correctly (`products` key). +3. Commit, push, PR + +--- + +## Help + +- If you see `type 'List' is not a subtype...` — you parsed the wrong level; use `['products']`. diff --git a/week-guides/week3/group-activity-1/group-4.md b/week-guides/week3/group-activity-1/group-4.md new file mode 100644 index 0000000..738170b --- /dev/null +++ b/week-guides/week3/group-activity-1/group-4.md @@ -0,0 +1,49 @@ +# Group 4 — Activity 1 (core) + +**Branch:** `week3-a1-group-4/dummyjson-users` + +--- + +## Your API (only yours) + +**Endpoint:** `https://dummyjson.com/users` +**Tip:** Top-level JSON has **`users`** (array), not a bare list. + +**Expected JSON:** + +| Part | Notes | +|------|--------| +| `users` | List of user objects | +| Each user | Often `id`, `firstName`, `lastName`, `email`, `image` | + +**Useful fields:** + +| Field | Type | Notes | +|--------|------|--------| +| `firstName` | String | Combine with `lastName` for one line | +| `lastName` | String | | +| `email` | String | Subtitle | + +--- + +## What everyone must implement (same for all groups) + +1. **Fetch** and parse **`users`** from the map. +2. **Display** in `ListView`. +3. **Loading** / **Error** / **Empty state** +4. **Provider** +5. **Refresh** +6. **(Optional)** Response time + +--- + +## Steps + +1. Branch: `week3-a1-group-4/dummyjson-users` +2. Commit, push, PR + +--- + +## Help + +- DummyJSON wraps many endpoints — always check the **first key** in the JSON. diff --git a/week-guides/week3/group-activity-1/group-5.md b/week-guides/week3/group-activity-1/group-5.md new file mode 100644 index 0000000..582dd2a --- /dev/null +++ b/week-guides/week3/group-activity-1/group-5.md @@ -0,0 +1,51 @@ +# Group 5 — Activity 1 (core) + +**Branch:** `week3-a1-group-5/reqres-users` + +--- + +## Your API (only yours) + +**Endpoint:** `https://reqres.in/api/users` +**Tip:** User rows are inside **`data`**, not at the top level. + +**Expected JSON:** + +| Part | Notes | +|------|--------| +| `data` | **List** of user objects | +| `page`, `per_page`, `total` | Pagination info (optional) | + +**Each item in `data` often has:** + +| Field | Type | Notes | +|--------|------|--------| +| `id` | int | | +| `email` | String | | +| `first_name` | String | | +| `last_name` | String | | +| `avatar` | String | URL — optional `Image.network` later | + +--- + +## What everyone must implement (same for all groups) + +1. **Fetch** → `jsonDecode` → loop **`map['data']`** as `List`. +2. **Display** — e.g. `first_name` + `last_name` or `email`. +3. **Loading** / **Error** / **Empty state** +4. **Provider** +5. **Refresh** +6. **(Optional)** Response time + +--- + +## Steps + +1. Branch: `week3-a1-group-5/reqres-users` +2. Commit, push, PR + +--- + +## Help + +- Wrong list? You probably used the root map as if it were a list — use **`['data']`**. diff --git a/week-guides/week3/group-activity-1/group-6.md b/week-guides/week3/group-activity-1/group-6.md new file mode 100644 index 0000000..3aa3e7c --- /dev/null +++ b/week-guides/week3/group-activity-1/group-6.md @@ -0,0 +1,45 @@ +# Group 6 — Activity 1 (core) + +**Branch:** `week3-a1-group-6/jsonplaceholder-posts` + +--- + +## Your API (only yours) + +**Endpoint:** `https://jsonplaceholder.typicode.com/posts` +**Tip:** Add `?_limit=15` if the list feels long. + +**Expected JSON:** A **JSON array** of posts. + +**Each post often has:** + +| Field | Type | Notes | +|--------|------|--------| +| `id` | int | | +| `userId` | int | | +| `title` | String | Short line in list | +| `body` | String | Optional second line (truncate if long) | + +--- + +## What everyone must implement (same for all groups) + +1. **Fetch** + parse array. +2. **Display** — **title** (required); **body** optional (first ~80 chars). +3. **Loading** / **Error** / **Empty state** +4. **Provider** +5. **Refresh** +6. **(Optional)** Response time + +--- + +## Steps + +1. Branch: `week3-a1-group-6/jsonplaceholder-posts` +2. Commit, push, PR + +--- + +## Help + +- Long `body` text: use `substring` with a max length or `Text` with `maxLines`. diff --git a/week-guides/week3/group-activity-1/group-7.md b/week-guides/week3/group-activity-1/group-7.md new file mode 100644 index 0000000..9970266 --- /dev/null +++ b/week-guides/week3/group-activity-1/group-7.md @@ -0,0 +1,49 @@ +# Group 7 — Activity 1 (core) + +**Branch:** `week3-a1-group-7/dummyjson-comments` + +--- + +## Your API (only yours) + +**Endpoint:** `https://dummyjson.com/comments` +**Tip:** Wrapper object with **`comments`** array. + +**Expected JSON:** + +| Part | Notes | +|------|--------| +| `comments` | List | +| Each item | Often `id`, `body`, `postId`, `user` (nested) | + +**Useful fields:** + +| Field | Type | Notes | +|--------|------|--------| +| `body` | String | Main text (may be long — truncate in list) | +| `id` | int | | +| `postId` | int | Optional subtitle | + +--- + +## What everyone must implement (same for all groups) + +1. **Fetch** → parse **`comments`** list. +2. **Display** — show **body** (truncated) or id + snippet. +3. **Loading** / **Error** / **Empty state** +4. **Provider** +5. **Refresh** +6. **(Optional)** Response time + +--- + +## Steps + +1. Branch: `week3-a1-group-7/dummyjson-comments` +2. Commit, push, PR + +--- + +## Help + +- If `user` is a nested map, access with care (`comment['user']?['username']` etc.) only if you need it. diff --git a/week-guides/week3/group-activity-1/group-8.md b/week-guides/week3/group-activity-1/group-8.md new file mode 100644 index 0000000..1b07fd1 --- /dev/null +++ b/week-guides/week3/group-activity-1/group-8.md @@ -0,0 +1,52 @@ +# Group 8 — Activity 1 (core) + +**Branch:** `week3-a1-group-8/publicapis-entries` + +--- + +## Your API (only yours) + +**Endpoint:** `https://api.publicapis.org/entries` +**Tip:** Add query params to limit size, e.g. `?category=animals` or check docs for `https` filter. Example: +`https://api.publicapis.org/entries?https=true` (large list — still consider handling many rows). + +**Expected JSON:** + +| Part | Notes | +|------|--------| +| `entries` | List of API metadata objects | +| `count` | Total count (optional) | + +**Each entry often has:** + +| Field | Type | Notes | +|--------|------|--------| +| `API` | String | Name of the public API | +| `Description` | String | Short text | +| `Category` | String | Optional chip or subtitle | +| `HTTPS` | bool | Optional icon | + +--- + +## What everyone must implement (same for all groups) + +1. **Fetch** → parse **`entries`** (not a top-level array). +2. **Display** — at least **API** name + one more field. +3. **Loading** / **Error** / **Empty state** +4. **Provider** +5. **Refresh** +6. **(Optional)** Response time + +--- + +## Steps + +1. Branch: `week3-a1-group-8/publicapis-entries` +2. If the list is huge, show first **N** items only (e.g. `take(30)`) for smooth UI. +3. Commit, push, PR + +--- + +## Help + +- Field names may use **capital letters** (`API`, `Description`) — match JSON keys exactly in Dart. diff --git a/week-guides/week3/group-activity-1/group-9.md b/week-guides/week3/group-activity-1/group-9.md new file mode 100644 index 0000000..c2c4cd8 --- /dev/null +++ b/week-guides/week3/group-activity-1/group-9.md @@ -0,0 +1,44 @@ +# Group 9 — Activity 1 (core) + +**Branch:** `week3-a1-group-9/jsonplaceholder-albums` + +--- + +## Your API (only yours) + +**Endpoint:** `https://jsonplaceholder.typicode.com/albums` +**Tip:** `?_limit=20` keeps it light. + +**Expected JSON:** A **JSON array** of albums. + +**Each item often has:** + +| Field | Type | Notes | +|--------|------|--------| +| `id` | int | | +| `userId` | int | Optional subtitle | +| `title` | String | Main line in list | + +--- + +## What everyone must implement (same for all groups) + +1. **Fetch** + parse array. +2. **Display** — **title** + optional `userId`. +3. **Loading** / **Error** / **Empty state** +4. **Provider** +5. **Refresh** +6. **(Optional)** Response time + +--- + +## Steps + +1. Branch: `week3-a1-group-9/jsonplaceholder-albums` +2. Commit, push, PR + +--- + +## Help + +- Same pattern as todos/posts — straight **List** from `jsonDecode`. diff --git a/week-guides/week3/group-activity-1/instructor-guide.md b/week-guides/week3/group-activity-1/instructor-guide.md new file mode 100644 index 0000000..4a93e9c --- /dev/null +++ b/week-guides/week3/group-activity-1/instructor-guide.md @@ -0,0 +1,74 @@ +# Group Activity 1 — Instructor guide (core implementation) + +--- + +## What students do + +**All groups complete the same skills.** Each group uses a **different API** (see `group-1.md` … `group-10.md`). + +Everyone must cover: + +- API fetch (`http`, `async`/`await`) +- Display data in the UI (e.g. `ListView`) +- Loading state +- Error handling +- **Provider** (`ChangeNotifier` + `ChangeNotifierProvider`) +- Refresh (button or pull-to-refresh) +- Empty state (friendly message when there is no data) +- *(Optional)* Log or show API response time (`DateTime` or `sample-code/performance_logger.dart`) + +--- + +## Recommended duration + +**55 minutes** total for this activity. + +--- + +## Suggested flow + +| Time | Focus | +|------|--------| +| **First 25 min** | API call + parse JSON + basic UI (list or cards) | +| **Next 20 min** | Move data into a **Provider**, wire loading / error / list | +| **Final 10 min** | Polish, empty state, refresh, debug | + +Adjust if your session is shorter — **understanding beats finishing**. + +--- + +## Checkpoint (~30 minutes) + +Pause the class and ask: + +- **“Did you successfully fetch data?”** (console or UI) +- **“Is your UI rendering correctly?”** (even a rough list is OK) + +Use answers to decide who needs help with **async** or **URLs** first. + +--- + +## Tips for you + +- Let **faster groups** continue to Provider and refresh **without waiting** for others. +- Help **struggling groups** with **async** and **API** first — Provider can follow once data appears. +- Focus on **understanding**, not 100% completion. + +--- + +## Teaching facilitation + +- Tell students: **do not aim only for completion** — aim to **understand** each step (why the screen updates, why errors happen). +- Point them to **Week 3 docs**: `theory/`, `diagrams/`, `sample-code/`, `resources/public-apis.md`. +- Ask out loud: + - **“Why are we using async/await here?”** + - **“Why do we need Provider?”** + +Short discussions help more than silent coding for everyone. + +--- + +## If time runs out + +- Minimum viable: **fetch + show list + loading + error**. +- Provider + refresh can be homework if needed. diff --git a/week-guides/week3/group-activity-2/group-1.md b/week-guides/week3/group-activity-2/group-1.md new file mode 100644 index 0000000..e23bb74 --- /dev/null +++ b/week-guides/week3/group-activity-2/group-1.md @@ -0,0 +1,32 @@ +# Group 1 — Activity 2 (advanced / optional) + +**Branch:** `week3-a2-group-1/jsonplaceholder-todos` + +**Same API as Activity 1:** `https://jsonplaceholder.typicode.com/todos` (e.g. `?_limit=20`) + +--- + +## Advanced tasks (same type for every group) + +Work on **your** Activity 1 code. Add as many as you can: + +- **Retry** — If `http.get` fails or status ≠ 200, retry up to **2–3 times** with a short pause (`Future.delayed`). +- **Latency** — Show **last request time in ms** on screen (use `DateTime` or `sample-code/performance_logger.dart`). +- **UI** — Cards, padding, or clearer typography (pick 1–2 improvements). +- **Cache** — Keep last good list in memory; on refresh, show old data until new data arrives. +- **Refresh** — `RefreshIndicator` or obvious refresh button (polish if you already have it). +- **Filter / search** — `TextField` that filters the **current list** by title (in memory). + +--- + +## Steps + +1. Continue from Activity 1 branch or create `week3-a2-group-1/jsonplaceholder-todos` from `main`. +2. Implement 2+ tasks above; prioritize **retry** + **latency** if time is short. +3. Commit, push, PR + +--- + +## Help + +- `group-activity-2/instructor-guide.md` — when to skip or simplify this activity. diff --git a/week-guides/week3/group-activity-2/group-10.md b/week-guides/week3/group-activity-2/group-10.md new file mode 100644 index 0000000..009f38a --- /dev/null +++ b/week-guides/week3/group-activity-2/group-10.md @@ -0,0 +1,30 @@ +# Group 10 — Activity 2 (advanced / optional) + +**Branch:** `week3-a2-group-10/dummyjson-carts` + +**Same API as Activity 1:** `https://dummyjson.com/carts` +Parse **`carts`**; nested **`products`** inside each cart. + +--- + +## Advanced tasks (same for all groups) + +- **Retry** +- **Latency** (ms) +- **UI** — Show totals **total** / **discountedTotal** if present in JSON. +- **Cache** last carts list. +- **Refresh** +- **Filter / search** — e.g. filter carts by **id** or **product count** (simple rules OK) + +--- + +## Steps + +1. Branch: `week3-a2-group-10/dummyjson-carts` +2. Commit, push, PR + +--- + +## Help + +- Nested JSON: test with `debugPrint` in small steps. diff --git a/week-guides/week3/group-activity-2/group-2.md b/week-guides/week3/group-activity-2/group-2.md new file mode 100644 index 0000000..ebde7e0 --- /dev/null +++ b/week-guides/week3/group-activity-2/group-2.md @@ -0,0 +1,29 @@ +# Group 2 — Activity 2 (advanced / optional) + +**Branch:** `week3-a2-group-2/jsonplaceholder-users` + +**Same API as Activity 1:** `https://jsonplaceholder.typicode.com/users` + +--- + +## Advanced tasks (same for all groups) + +- **Retry** — 2–3 attempts on failure. +- **Latency** — Show last load time (ms) in UI. +- **UI** — Improve list appearance (cards, spacing). +- **Cache** — Keep last successful list; show while refreshing. +- **Refresh** — Pull-to-refresh or clear refresh UX. +- **Filter / search** — Filter by **name** or **email** (substring match). + +--- + +## Steps + +1. Branch: `week3-a2-group-2/jsonplaceholder-users` +2. Add features from the list; commit, push, PR + +--- + +## Help + +- See `week3/theory/` and `sample-code/performance_logger.dart` diff --git a/week-guides/week3/group-activity-2/group-3.md b/week-guides/week3/group-activity-2/group-3.md new file mode 100644 index 0000000..c495b32 --- /dev/null +++ b/week-guides/week3/group-activity-2/group-3.md @@ -0,0 +1,24 @@ +# Group 3 — Activity 2 (advanced / optional) + +**Branch:** `week3-a2-group-3/dummyjson-products` + +**Same API as Activity 1:** `https://dummyjson.com/products` +Remember: items are in **`products`**. + +--- + +## Advanced tasks (same for all groups) + +- **Retry** — On failure, retry a few times. +- **Latency** — Display ms for last request. +- **UI** — e.g. show **price** clearly, use `Card`. +- **Cache** — Last `products` list in memory during refresh. +- **Refresh** — `RefreshIndicator` + `onRefresh`. +- **Filter / search** — filter by **title** (or price range if you want a challenge). + +--- + +## Steps + +1. Branch: `week3-a2-group-3/dummyjson-products` +2. Commit, push, PR diff --git a/week-guides/week3/group-activity-2/group-4.md b/week-guides/week3/group-activity-2/group-4.md new file mode 100644 index 0000000..38e3239 --- /dev/null +++ b/week-guides/week3/group-activity-2/group-4.md @@ -0,0 +1,24 @@ +# Group 4 — Activity 2 (advanced / optional) + +**Branch:** `week3-a2-group-4/dummyjson-users` + +**Same API as Activity 1:** `https://dummyjson.com/users` +Parse **`users`** array. + +--- + +## Advanced tasks (same for all groups) + +- **Retry** mechanism +- **Latency** in UI (ms) +- **UI** improvements +- **Cache** last list in memory +- **Refresh** (polish) +- **Filter / search** on **firstName**, **lastName**, or **email** + +--- + +## Steps + +1. Branch: `week3-a2-group-4/dummyjson-users` +2. Commit, push, PR diff --git a/week-guides/week3/group-activity-2/group-5.md b/week-guides/week3/group-activity-2/group-5.md new file mode 100644 index 0000000..54d1414 --- /dev/null +++ b/week-guides/week3/group-activity-2/group-5.md @@ -0,0 +1,24 @@ +# Group 5 — Activity 2 (advanced / optional) + +**Branch:** `week3-a2-group-5/reqres-users` + +**Same API as Activity 1:** `https://reqres.in/api/users` +Users live in **`data`**. + +--- + +## Advanced tasks (same for all groups) + +- **Retry** — Multiple attempts on error. +- **Latency** — Show ms after load. +- **UI** — Avatars optional (`Image.network`); keep layout simple if short on time. +- **Cache** — Store last `data` list. +- **Refresh** +- **Filter / search** — By **name** or **email** + +--- + +## Steps + +1. Branch: `week3-a2-group-5/reqres-users` +2. Commit, push, PR diff --git a/week-guides/week3/group-activity-2/group-6.md b/week-guides/week3/group-activity-2/group-6.md new file mode 100644 index 0000000..b3ea228 --- /dev/null +++ b/week-guides/week3/group-activity-2/group-6.md @@ -0,0 +1,23 @@ +# Group 6 — Activity 2 (advanced / optional) + +**Branch:** `week3-a2-group-6/jsonplaceholder-posts` + +**Same API as Activity 1:** `https://jsonplaceholder.typicode.com/posts` (optional `?_limit=15`) + +--- + +## Advanced tasks (same for all groups) + +- **Retry** +- **Latency** display +- **UI** — Truncate long titles/bodies nicely (`maxLines`, `ellipsis`) +- **Cache** +- **Refresh** +- **Filter / search** — By **title** substring + +--- + +## Steps + +1. Branch: `week3-a2-group-6/jsonplaceholder-posts` +2. Commit, push, PR diff --git a/week-guides/week3/group-activity-2/group-7.md b/week-guides/week3/group-activity-2/group-7.md new file mode 100644 index 0000000..ab107bc --- /dev/null +++ b/week-guides/week3/group-activity-2/group-7.md @@ -0,0 +1,24 @@ +# Group 7 — Activity 2 (advanced / optional) + +**Branch:** `week3-a2-group-7/dummyjson-comments` + +**Same API as Activity 1:** `https://dummyjson.com/comments` +Use **`comments`** list. + +--- + +## Advanced tasks (same for all groups) + +- **Retry** +- **Latency** (ms) +- **UI** — Readable snippets of long `body` text +- **Cache** +- **Refresh** +- **Filter / search** — In **body** text + +--- + +## Steps + +1. Branch: `week3-a2-group-7/dummyjson-comments` +2. Commit, push, PR diff --git a/week-guides/week3/group-activity-2/group-8.md b/week-guides/week3/group-activity-2/group-8.md new file mode 100644 index 0000000..34415fd --- /dev/null +++ b/week-guides/week3/group-activity-2/group-8.md @@ -0,0 +1,25 @@ +# Group 8 — Activity 2 (advanced / optional) + +**Branch:** `week3-a2-group-8/publicapis-entries` + +**Same API as Activity 1:** `https://api.publicapis.org/entries` (use query params if needed) + +Parse **`entries`**. + +--- + +## Advanced tasks (same for all groups) + +- **Retry** — Useful for slow APIs. +- **Latency** — Show load time; compare with Activity 1 runs. +- **UI** — Category as chip or subtitle. +- **Cache** — Last `entries` slice you display. +- **Refresh** +- **Filter / search** — By **API** name or **Category** + +--- + +## Steps + +1. Branch: `week3-a2-group-8/publicapis-entries` +2. Commit, push, PR diff --git a/week-guides/week3/group-activity-2/group-9.md b/week-guides/week3/group-activity-2/group-9.md new file mode 100644 index 0000000..176b2d4 --- /dev/null +++ b/week-guides/week3/group-activity-2/group-9.md @@ -0,0 +1,23 @@ +# Group 9 — Activity 2 (advanced / optional) + +**Branch:** `week3-a2-group-9/jsonplaceholder-albums` + +**Same API as Activity 1:** `https://jsonplaceholder.typicode.com/albums` (`?_limit=20`) + +--- + +## Advanced tasks (same for all groups) + +- **Retry** +- **Latency** in UI +- **UI** — Improve list / cards +- **Cache** +- **Refresh** +- **Filter / search** — By **title** + +--- + +## Steps + +1. Branch: `week3-a2-group-9/jsonplaceholder-albums` +2. Commit, push, PR diff --git a/week-guides/week3/group-activity-2/instructor-guide.md b/week-guides/week3/group-activity-2/instructor-guide.md new file mode 100644 index 0000000..aae6bc6 --- /dev/null +++ b/week-guides/week3/group-activity-2/instructor-guide.md @@ -0,0 +1,58 @@ +# Group Activity 2 — Instructor guide (advanced / optional) + +--- + +## What this is + +**Same APIs as Activity 1** (each group keeps **their** endpoint). **Same type of advanced work** for everyone: + +- Retry when a request fails +- Show **API response time** in the UI or console (ms) +- UI improvements (spacing, cards, readable text) +- **Basic caching** (keep last successful list in memory; show it while refreshing) +- **Refresh** (clearer UX — e.g. `RefreshIndicator`) +- **Filtering / search** (narrow the list in memory by title or name) + +--- + +## Recommended duration + +- **45 minutes** (typical) +- **Extend to 60 minutes** if the class is doing well and you want deeper demos + +--- + +## When to run this activity + +- This block is **OPTIONAL** — use it only if it fits your schedule. +- **Start only if** the **majority of groups** finished the **core** pieces of Activity 1 (fetch + UI + at least a plan for Provider). + +--- + +## Instructor decision + +- If **many groups struggled** in Activity 1 → **skip** Activity 2, or **simplify** (e.g. only retry + one UI tweak). +- If the class is strong → run full list above. + +--- + +## Tips for you + +- Same as Activity 1: **understanding > ticking every box**. +- Encourage students to reuse **Week 3** notes and `sample-code/performance_logger.dart` for timing. + +--- + +## Teaching facilitation + +- Remind students: **completion is not the only success** — explaining **why** retry or cache helps is valuable. +- Send them back to **`week3/theory/`** and **`sample-code/`** when stuck. +- Ask: + - **“Why are we using async/await here?”** + - **“Why did we put this in Provider?”** + +--- + +## If time runs out + +- Pick **two** items (e.g. retry + latency text) instead of all six. diff --git a/week-guides/week3/resources/public-apis.md b/week-guides/week3/resources/public-apis.md new file mode 100644 index 0000000..e58641e --- /dev/null +++ b/week-guides/week3/resources/public-apis.md @@ -0,0 +1,63 @@ +# Public APIs for practice + +These services are **free for learning**. Always read their rules; don’t spam them with huge traffic. + +--- + +## JSONPlaceholder + +**URL:** https://jsonplaceholder.typicode.com + +**What it is:** Fake REST API with posts, users, todos, albums — great for learning **GET** without signing up. + +**Example endpoint:** +`GET https://jsonplaceholder.typicode.com/todos?_limit=5` + +**Try:** Fetch todos or posts, show them in a `ListView`, parse JSON into a model class. + +--- + +## ReqRes + +**URL:** https://reqres.in + +**What it is:** Fake data for **users**, login-style examples, pagination — useful for **lists** and **POST** practice. + +**Example endpoint:** +`GET https://reqres.in/api/users?page=1` + +**Try:** Display user avatars and names; compare response shape with JSONPlaceholder. + +--- + +## DummyJSON + +**URL:** https://dummyjson.com + +**What it is:** Rich fake data: products, users, quotes, carts — good for slightly **more complex** JSON. + +**Example endpoint:** +`GET https://dummyjson.com/products?limit=5` + +**Try:** Build a product list with title + price; handle the nested JSON fields. + +--- + +## Public APIs list (meta) + +**URL:** https://api.publicapis.org + +**What it is:** A **directory** of many public APIs (not one fake dataset — a **catalog** you can search). + +**Example endpoint:** +`GET https://api.publicapis.org/entries?category=animals&https=true` + +**Try:** Pick one small API from the results and call it from Flutter (check CORS / HTTPS for web if you use Flutter web). + +--- + +## Tips for students + +- Start with **GET** and **HTTPS**. +- Use **`?limit=`** or similar when the API supports it so responses stay small. +- If a call fails, read the **status code** and **body** in the debugger. diff --git a/week-guides/week3/sample-code/README.md b/week-guides/week3/sample-code/README.md new file mode 100644 index 0000000..0b4e37e --- /dev/null +++ b/week-guides/week3/sample-code/README.md @@ -0,0 +1,60 @@ +# Week 3 sample code + +Copy these files into your Flutter app’s `lib/` folder (e.g. `lib/week3/`) and adjust imports. + +## Dependencies + +In `pubspec.yaml`: + +```yaml +dependencies: + flutter: + sdk: flutter + http: ^1.2.0 + provider: ^6.1.0 +``` + +Then run: + +```bash +flutter pub get +``` + +## Files + +| File | Role | +|------|------| +| `todo_model.dart` | Data class for one todo item | +| `api_service.dart` | Fetches JSON from the network | +| `todo_provider.dart` | `ChangeNotifier` — loading, error, list | +| `example_screen.dart` | List UI + refresh | +| `performance_logger.dart` | Measures how long a call takes (ms) | + +## Full flow (Provider) + +1. UI calls `TodoProvider.fetchTodos()` +2. Provider sets `loading = true` and calls `notifyListeners()` +3. Service (`api_service.dart`) fetches todos +4. Provider updates `todos` or `error` +5. Provider calls `notifyListeners()` again +6. UI rebuilds and shows loading/data/error + +## Wire up in `main.dart` (sketch) + +```dart +import 'package:provider/provider.dart'; +// import your paths... + +void main() { + runApp( + ChangeNotifierProvider( + create: (_) => TodoProvider()..fetchTodos(), + child: const MaterialApp( + home: ExampleScreen(), + ), + ), + ); +} +``` + +Use your real `MaterialApp` theme and routes if you already have them. diff --git a/week-guides/week3/sample-code/api_service.dart b/week-guides/week3/sample-code/api_service.dart new file mode 100644 index 0000000..31bb781 --- /dev/null +++ b/week-guides/week3/sample-code/api_service.dart @@ -0,0 +1,28 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; + +import 'todo_model.dart'; + +/// Fetches todos from a free demo API (JSONPlaceholder). +class ApiService { + ApiService({http.Client? client}) : _client = client ?? http.Client(); + + final http.Client _client; + + static final Uri todosUri = + Uri.parse('https://jsonplaceholder.typicode.com/todos?_limit=10'); + + Future> fetchTodos() async { + final response = await _client.get(todosUri); + + if (response.statusCode != 200) { + throw Exception('HTTP ${response.statusCode}'); + } + + final list = jsonDecode(response.body) as List; + return list + .map((e) => Todo.fromJson(e as Map)) + .toList(); + } +} diff --git a/week-guides/week3/sample-code/example_screen.dart b/week-guides/week3/sample-code/example_screen.dart new file mode 100644 index 0000000..bf318da --- /dev/null +++ b/week-guides/week3/sample-code/example_screen.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import 'todo_provider.dart'; + +/// Example screen: shows loading, error, or a list from [TodoProvider]. +class ExampleScreen extends StatelessWidget { + const ExampleScreen({super.key}); + + @override + Widget build(BuildContext context) { + final provider = context.watch(); + + return Scaffold( + appBar: AppBar( + title: const Text('Week 3 — Todos'), + ), + body: _buildBody(context, provider), + floatingActionButton: FloatingActionButton( + onPressed: () => context.read().refresh(), + child: const Icon(Icons.refresh), + ), + ); + } + + Widget _buildBody(BuildContext context, TodoProvider provider) { + if (provider.loading && provider.todos.isEmpty) { + return const Center(child: CircularProgressIndicator()); + } + + if (provider.error != null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Something went wrong', + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + Text( + provider.error!, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + FilledButton( + onPressed: () => provider.fetchTodos(), + child: const Text('Retry'), + ), + ], + ), + ), + ); + } + + final todos = provider.todos; + if (todos.isEmpty) { + return const Center(child: Text('No todos yet.')); + } + + return ListView.builder( + itemCount: todos.length, + itemBuilder: (context, index) { + final todo = todos[index]; + return ListTile( + leading: Icon( + todo.completed ? Icons.check_circle : Icons.radio_button_unchecked, + ), + title: Text(todo.title), + ); + }, + ); + } +} diff --git a/week-guides/week3/sample-code/performance_logger.dart b/week-guides/week3/sample-code/performance_logger.dart new file mode 100644 index 0000000..2d010a8 --- /dev/null +++ b/week-guides/week3/sample-code/performance_logger.dart @@ -0,0 +1,46 @@ +/// Measures how long something takes — useful for comparing API speeds. +/// +/// Records [start] and [end] with [DateTime.now] and prints duration in ms. +class PerformanceLogger { + PerformanceLogger({DateTime? start}) + : start = start ?? DateTime.now(); + + DateTime start; + DateTime? end; + + /// Call when the work finishes. + void finish({String label = 'Done'}) { + end = DateTime.now(); + final ms = durationMs; + // ignore: avoid_print + print('[PerformanceLogger] $label: ${ms}ms'); + } + + /// Milliseconds between [start] and [end], or 0 if not finished. + int get durationMs { + if (end == null) return 0; + return end!.difference(start).inMilliseconds; + } + + /// Runs [action], records time, prints duration, returns the result. + static Future measure( + Future Function() action, { + String label = 'operation', + }) async { + final start = DateTime.now(); + try { + final result = await action(); + final end = DateTime.now(); + final ms = end.difference(start).inMilliseconds; + // ignore: avoid_print + print('[PerformanceLogger] $label took ${ms}ms'); + return result; + } catch (e) { + final end = DateTime.now(); + final ms = end.difference(start).inMilliseconds; + // ignore: avoid_print + print('[PerformanceLogger] $label failed after ${ms}ms: $e'); + rethrow; + } + } +} diff --git a/week-guides/week3/sample-code/todo_model.dart b/week-guides/week3/sample-code/todo_model.dart new file mode 100644 index 0000000..2f230ac --- /dev/null +++ b/week-guides/week3/sample-code/todo_model.dart @@ -0,0 +1,30 @@ +/// One row from JSONPlaceholder `/todos`. +class Todo { + const Todo({ + required this.id, + required this.userId, + required this.title, + required this.completed, + }); + + final int id; + final int userId; + final String title; + final bool completed; + + factory Todo.fromJson(Map json) { + return Todo( + id: json['id'] as int, + userId: json['userId'] as int, + title: json['title'] as String? ?? '', + completed: json['completed'] as bool? ?? false, + ); + } + + Map toJson() => { + 'id': id, + 'userId': userId, + 'title': title, + 'completed': completed, + }; +} diff --git a/week-guides/week3/sample-code/todo_provider.dart b/week-guides/week3/sample-code/todo_provider.dart new file mode 100644 index 0000000..c6bed14 --- /dev/null +++ b/week-guides/week3/sample-code/todo_provider.dart @@ -0,0 +1,55 @@ +import 'package:flutter/foundation.dart'; + +import 'api_service.dart'; +import 'todo_model.dart'; + +/// Beginner-friendly Provider example: +/// - fetches todos from API +/// - exposes loading/error/data state +/// - calls notifyListeners so UI rebuilds +class TodoProvider extends ChangeNotifier { + TodoProvider({ApiService? api}) : _api = api ?? ApiService(); + + final ApiService _api; + + List _todos = []; + bool _loading = false; + String? _error; + + List get todos => List.unmodifiable(_todos); + bool get loading => _loading; + String? get error => _error; + + /// Full flow: + /// 1) set loading + /// 2) call API + /// 3) save data or error + /// 4) notify UI + Future fetchTodos() async { + _loading = true; + _error = null; + notifyListeners(); + + try { + _todos = await _api.fetchTodos(); + } catch (e) { + _error = e.toString(); + _todos = []; + } finally { + _loading = false; + notifyListeners(); + } + } + + /// Alias kept for compatibility with older docs/screens. + Future loadTodos() => fetchTodos(); + + /// Refresh button can call the same method. + Future refresh() => fetchTodos(); + + /// Simple setter example for teaching manual updates. + void setTodos(List newTodos) { + _todos = List.from(newTodos); + notifyListeners(); + } +} diff --git a/week-guides/week3/theory/api-and-async.md b/week-guides/week3/theory/api-and-async.md new file mode 100644 index 0000000..540805b --- /dev/null +++ b/week-guides/week3/theory/api-and-async.md @@ -0,0 +1,155 @@ +# HTTP APIs and async in Flutter + +--- + +## HTTP requests in Flutter + +Mobile apps often load data from the **internet** using **HTTP** (usually **REST** APIs). The server sends **JSON** text; your app turns it into Dart objects. + +Flutter does not load URLs by itself in the widget tree — you use a package like **`http`**. + +Add to `pubspec.yaml`: + +```yaml +dependencies: + http: ^1.2.0 +``` + +Then: + +```dart +import 'package:http/http.dart' as http; +``` + +--- + +## Why async is required for APIs + +A network request is **slow** (milliseconds to seconds). If you waited **synchronously**, the UI would **freeze**. + +So `http.get` returns a **`Future`** — you **`await`** it inside an **`async`** function. + +--- + +## Example: `http.get` + +```dart +import 'package:http/http.dart' as http; + +Future fetchTitle() async { + final uri = Uri.parse('https://jsonplaceholder.typicode.com/posts/1'); + final response = await http.get(uri); + + if (response.statusCode == 200) { + return response.body; // raw JSON string + } else { + throw Exception('Failed to load: ${response.statusCode}'); + } +} +``` + +- **`statusCode` `200`** usually means success. +- **`response.body`** is the text (often JSON). + +--- + +## What `Uri.parse` does + +`http.get` expects a **`Uri`** object, not a plain string. + +So this line: + +```dart +final uri = Uri.parse('https://jsonplaceholder.typicode.com/posts/1'); +``` + +means: + +- take a URL string +- convert it to a structured `Uri` +- make it ready for `http.get(uri)` + +You can also build URLs more safely with `Uri.https` when query values are dynamic: + +```dart +final uri = Uri.https( + 'jsonplaceholder.typicode.com', + '/posts', + {'_limit': '10'}, +); +``` + +This helps avoid mistakes when building query strings manually. + +--- + +## Error handling + +Networks fail (no Wi‑Fi, server error, timeout). Always use **`try` / `catch`** (and show a message in the UI). + +```dart +Future safeFetch() async { + try { + final uri = Uri.parse('https://jsonplaceholder.typicode.com/posts/1'); + final response = await http.get(uri); + if (response.statusCode == 200) { + return response.body; + } + throw Exception('Bad status: ${response.statusCode}'); + } catch (e) { + print('Error: $e'); + return null; + } +} +``` + +In a real screen, set an **error state** (e.g. `String? errorMessage`) and call **`setState`** or update a **Provider** so the user sees a friendly message. + +--- + +## JSON + +APIs return JSON strings. Use **`dart:convert`**: + +```dart +import 'dart:convert'; + +final map = jsonDecode(response.body) as Map; +final title = map['title'] as String?; +``` + +### What `jsonDecode` does + +`jsonDecode` converts JSON **text** into Dart objects. + +- JSON object (`{}`) -> `Map` +- JSON array (`[]`) -> `List` + +Example with a list endpoint: + +```dart +final list = jsonDecode(response.body) as List; +``` + +Then convert each item to your model: + +```dart +final todos = list + .map((e) => Todo.fromJson(e as Map)) + .toList(); +``` + +So the flow is: + +1. `response.body` (String) +2. `jsonDecode(...)` (List/Map) +3. map into typed models (`Todo`) + +Build a **model class** for cleaner code (see `sample-code/todo_model.dart`). + +--- + +## Next + +- [state-management.md](state-management.md) — show loading / data / error in the UI. +- [../resources/public-apis.md](../resources/public-apis.md) — URLs to practice with. diff --git a/week-guides/week3/theory/async-await.md b/week-guides/week3/theory/async-await.md new file mode 100644 index 0000000..aae776c --- /dev/null +++ b/week-guides/week3/theory/async-await.md @@ -0,0 +1,91 @@ +# Async programming: `Future`, `async`, and `await` + +--- + +## Synchronous vs asynchronous + +**Synchronous** code runs line by line. The next line waits until the current line finishes. + +**Asynchronous** code can **start** something and **come back later** when it is done. While you wait, the app can still draw the UI and respond to taps. + +In Flutter, **you must not block the UI thread** with long work (big loops, waiting for the network). Async code helps you schedule that work without freezing the screen. + +--- + +## Real-world analogy: restaurant + +- **Synchronous:** You stand at the counter until your food is ready. Nothing else happens. +- **Asynchronous:** You get a **buzzer**. You sit down. When the buzzer rings (**later**), you pick up your food. + +`Future` is like the buzzer — a **promise** that a value will arrive later. + +--- + +## What is a `Future`? + +A **`Future`** means: “I will give you a value of type `T` **in the future**.” + +```dart +Future fetchMessage() async { + await Future.delayed(const Duration(seconds: 1)); + return 'Hello!'; +} +``` + +- If something returns `Future`, the work may finish **after** the function returns. +- You use **`await`** inside an **`async`** function to **wait** for that result **without** blocking the whole app badly (the runtime can do other work). + +--- + +## `async` and `await` + +- **`async`** — marks a function that can use `await` and returns a `Future` automatically. +- **`await`** — pauses **this** async function until the `Future` completes, then gives you the result. + +```dart +void load() async { + final text = await fetchMessage(); // wait here inside load() + print(text); +} +``` + +--- + +## Example: `Future.delayed` + +```dart +Future demo() async { + print('Start'); + await Future.delayed(const Duration(seconds: 2)); + print('2 seconds later'); +} +``` + +`Future.delayed` returns a `Future` that completes after the delay. + +--- + +## Common mistakes + +1. **Forgetting `await`** + You get a `Future` object instead of the real value. + + ```dart + // Wrong for printing the string: + final x = fetchMessage(); // x is Future, not String! + ``` + +2. **Using `await` in a non-`async` function** + Dart will not allow it — mark the function `async`. + +3. **Blocking the UI on purpose** + Avoid huge synchronous work on the main isolate. For heavy CPU work, look up **isolates** later; for network, use `async`/`await` with `http`. + +4. **Not handling errors** + Use `try` / `catch` around `await` calls that can fail (see `api-and-async.md`). + +--- + +## Next + +Read [api-and-async.md](api-and-async.md) to connect async code to real HTTP requests. diff --git a/week-guides/week3/theory/prop-drilling.md b/week-guides/week3/theory/prop-drilling.md new file mode 100644 index 0000000..68c0340 --- /dev/null +++ b/week-guides/week3/theory/prop-drilling.md @@ -0,0 +1,49 @@ +# Prop drilling + +--- + +## The problem + +Imagine you have: + +`App` → `HomePage` → `TodoList` → `TodoTile` + +The **user name** (or **todo list**) lives at the top, but only **`TodoTile`** needs to show it. + +You end up **passing the same data through every layer**: + +```dart +App(userName: name) + → HomePage(userName: name) + → TodoList(userName: name) + → TodoTile(userName: name) +``` + +This is **prop drilling**: passing props through widgets that **don’t use** them, only to reach a **child** far below. + +--- + +## Why it hurts + +- Many files change when you add one new field. +- Easy to make mistakes (wrong parameter order). +- Hard to read and maintain. + +--- + +## What to do instead (preview) + +For **deep** or **shared** data, lift state up and use a **single place** everyone can read: + +- **Provider** (this course) +- Or other solutions (Riverpod, Bloc, etc.) later + +See [provider.md](provider.md). + +--- + +## Simple mental picture + +**Prop drilling** = passing a backpack hand-to-hand through a long line of people. + +**Provider** = putting the backpack on a **hook on the wall** — anyone who needs it can take it, without passing through every person. diff --git a/week-guides/week3/theory/provider-best-practices.md b/week-guides/week3/theory/provider-best-practices.md new file mode 100644 index 0000000..e38d4ba --- /dev/null +++ b/week-guides/week3/theory/provider-best-practices.md @@ -0,0 +1,63 @@ +# Provider Best Practices + +--- + +## 1) Keep business logic out of UI + +Avoid writing API parsing and state rules directly in widgets. + +- UI should focus on layout and user actions +- Provider/service should handle data logic + +--- + +## 2) Keep API calls in provider or service + +A clean pattern: + +- Service: HTTP requests + parsing +- Provider: loading/error/data state +- UI: display and interactions + +--- + +## 3) Avoid unnecessary provider calls in `build()` + +Do not trigger network calls repeatedly inside `build()`. + +Bad pattern: + +```dart +@override +Widget build(BuildContext context) { + Provider.of(context, listen: false).fetchTodos(); // avoid + ... +} +``` + +Better: + +- Call once from `initState()` in a `StatefulWidget` +- Or trigger from user actions (button, pull to refresh) + +--- + +## 4) Keep separation of concerns + +Good separation makes code easier to test and teach: + +- `api_service.dart` -> talks to server +- `todo_provider.dart` -> app state + notify +- `example_screen.dart` -> UI + +--- + +## 5) Handle loading, error, empty explicitly + +In provider, keep clear state fields: + +- `loading` +- `error` +- `todos` + +This gives students a predictable UI pattern. diff --git a/week-guides/week3/theory/provider-consume.md b/week-guides/week3/theory/provider-consume.md new file mode 100644 index 0000000..d3cbcc8 --- /dev/null +++ b/week-guides/week3/theory/provider-consume.md @@ -0,0 +1,57 @@ +# Consume Provider in UI + +--- + +## Read data using `Provider.of` + +```dart +final todos = Provider.of(context).todos; +``` + +This reads provider data in the widget tree. + +--- + +## `listen: true` vs `listen: false` + +`listen: true` (default): + +- Widget rebuilds when provider calls `notifyListeners()` +- Good for showing values in UI + +`listen: false`: + +- Widget does not rebuild from this read +- Good for button actions and method calls + +Examples: + +```dart +// Rebuild when data changes +final provider = Provider.of(context); // listen: true + +// No rebuild, just call method +Provider.of(context, listen: false).fetchTodos(); +``` + +--- + +## Using `Consumer` (optional) + +`Consumer` helps rebuild only a small part of UI: + +```dart +Consumer( + builder: (context, provider, child) { + return Text('Items: ${provider.todos.length}'); + }, +) +``` + +This can be cleaner and more efficient than rebuilding the whole screen. + +--- + +## Key idea + +When provider state changes and `notifyListeners()` runs, listening UI rebuilds automatically. diff --git a/week-guides/week3/theory/provider-create.md b/week-guides/week3/theory/provider-create.md new file mode 100644 index 0000000..3ff0eea --- /dev/null +++ b/week-guides/week3/theory/provider-create.md @@ -0,0 +1,74 @@ +# Create a Provider with `ChangeNotifier` + +--- + +## What is `ChangeNotifier`? + +`ChangeNotifier` is a Flutter class that can tell the UI: + +"My data changed, rebuild listeners." + +It does this with `notifyListeners()`. + +--- + +## Simple provider example + +```dart +class TodoProvider extends ChangeNotifier { + List todos = []; + bool isLoading = false; + + void setTodos(List newTodos) { + todos = newTodos; + notifyListeners(); + } +} +``` + +--- + +## Why `notifyListeners()` is needed + +If you update data but forget `notifyListeners()`, widgets may not rebuild. + +So this is important: + +1. Change state +2. Call `notifyListeners()` + +--- + +## Add loading state too + +```dart +class TodoProvider extends ChangeNotifier { + List todos = []; + bool isLoading = false; + + void setLoading(bool value) { + isLoading = value; + notifyListeners(); + } + + void setTodos(List newTodos) { + todos = newTodos; + notifyListeners(); + } +} +``` + +--- + +## Register provider in `main.dart` + +```dart +runApp( + ChangeNotifierProvider( + create: (_) => TodoProvider(), + child: const MyApp(), + ), +); +``` + +Now widgets under `MyApp` can access `TodoProvider`. diff --git a/week-guides/week3/theory/provider-introduction.md b/week-guides/week3/theory/provider-introduction.md new file mode 100644 index 0000000..18464a1 --- /dev/null +++ b/week-guides/week3/theory/provider-introduction.md @@ -0,0 +1,77 @@ +# Provider Introduction (Beginner Friendly) + +--- + +## What is state? + +State is data your UI needs right now. + +Examples: + +- Is data loading? +- What items are in a list? +- Did an error happen? + +When state changes, the UI should update. + +--- + +## Local vs Global state + +**Local state**: + +- Used by one widget or one screen +- Usually managed with `setState` + +**Global/shared state**: + +- Needed by many widgets/screens +- Should live in one shared place + +--- + +## The prop drilling problem + +Sometimes data is needed deep in the widget tree. + +Without Provider, you may pass the same value through many constructors: + +`App -> Screen -> Section -> Tile` + +This is called **prop drilling**. It makes code noisy and harder to maintain. + +--- + +## Why Provider is needed + +Provider gives your app a shared state object that many widgets can read. + +- One place to store state +- Easy way to update state +- UI rebuilds when state changes + +--- + +## Analogy: shared whiteboard for the app + +Think of Provider as a **shared whiteboard** in a classroom: + +- One student writes updates on the board (Provider updates state) +- Everyone can look at the same board (widgets read state) +- When board content changes, everyone sees the latest version (UI rebuild) + +--- + +## Common mistakes + +- Forgetting `notifyListeners()` after updating state +- Using `Provider.of` with wrong `listen` value +- Calling expensive provider methods repeatedly from `build()` + +--- + +## Next + +- Create provider: `provider-create.md` +- Read from UI: `provider-consume.md` +- Update state from UI: `provider-update.md` diff --git a/week-guides/week3/theory/provider-update.md b/week-guides/week3/theory/provider-update.md new file mode 100644 index 0000000..17c8171 --- /dev/null +++ b/week-guides/week3/theory/provider-update.md @@ -0,0 +1,50 @@ +# Update Provider State from UI + +--- + +## Update by calling provider methods + +You usually update state by calling a method on provider, not by editing fields in UI files. + +```dart +Provider.of(context, listen: false).setTodos(newTodos); +``` + +--- + +## Why `listen: false` here? + +In button handlers, we want to call a method, not rebuild because of that read. + +So we use: + +```dart +listen: false +``` + +This keeps UI logic clear and avoids unnecessary rebuilds. + +--- + +## Common update flow + +1. User taps a button +2. UI calls provider method +3. Provider updates state +4. Provider calls `notifyListeners()` +5. Listening widgets rebuild + +--- + +## Example + +```dart +FilledButton( + onPressed: () { + Provider.of(context, listen: false).fetchTodos(); + }, + child: const Text('Refresh'), +) +``` + +The screen updates when `fetchTodos()` completes and notifies listeners. diff --git a/week-guides/week3/theory/provider.md b/week-guides/week3/theory/provider.md new file mode 100644 index 0000000..1aff392 --- /dev/null +++ b/week-guides/week3/theory/provider.md @@ -0,0 +1,83 @@ +# Provider & global state + +--- + +## Global state (in simple terms) + +**Global state** here means: data that **more than one part** of the widget tree needs, without passing it through every constructor. + +We still want **one place** that **owns** the data and **tells** the UI when it changes. + +--- + +## `ChangeNotifier` + +`ChangeNotifier` is a class from Flutter that can **`notifyListeners()`**. Widgets listening to it will **rebuild**. + +```dart +class CounterModel extends ChangeNotifier { + int count = 0; + + void increment() { + count++; + notifyListeners(); + } +} +``` + +--- + +## Provider package + +Add to `pubspec.yaml`: + +```yaml +dependencies: + provider: ^6.1.0 +``` + +**Put** the model **above** the widgets that need it: + +```dart +void main() { + runApp( + ChangeNotifierProvider( + create: (_) => CounterModel(), + child: const MyApp(), + ), + ); +} +``` + +--- + +## Read and listen in the UI + +**`context.watch()`** — rebuild when `T` changes. +**`context.read()`** — get it once (e.g. inside `onPressed`), no rebuild from this line. + +```dart +final model = context.watch(); +// Use model.count in build() + +// In a button: +onPressed: () => context.read().increment(), +``` + +--- + +## Basic flow + +1. Create a class extending **`ChangeNotifier`**. +2. Wrap your app (or a subtree) with **`ChangeNotifierProvider`**. +3. Call **`notifyListeners()`** after you change data. +4. Use **`watch`** / **`read`** in descendants. + +See `sample-code/todo_provider.dart` and `example_screen.dart` for a full mini-example. + +--- + +## Next + +- `diagrams/data-flow.md` — API → Provider → UI. +- `group-activity-1/` — practice step by step. diff --git a/week-guides/week3/theory/state-management.md b/week-guides/week3/theory/state-management.md new file mode 100644 index 0000000..530309d --- /dev/null +++ b/week-guides/week3/theory/state-management.md @@ -0,0 +1,73 @@ +# State management (basics) + +--- + +## What is “state”? + +**State** is the data your app needs **right now** to draw the screen: + +- Is the list **loading**? +- What **todos** do we have? +- Did the user **toggle** dark mode? + +When state **changes**, the UI should **update** (rebuild). + +--- + +## Local state with `setState` + +Inside a **`StatefulWidget`**, you store data in the **`State`** class. When you change that data, you call **`setState`** so Flutter rebuilds the widget. + +```dart +class CounterPage extends StatefulWidget { + const CounterPage({super.key}); + + @override + State createState() => _CounterPageState(); +} + +class _CounterPageState extends State { + int count = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center(child: Text('$count')), + floatingActionButton: FloatingActionButton( + onPressed: () { + setState(() { + count++; + }); + }, + child: const Icon(Icons.add), + ), + ); + } +} +``` + +- **`setState`** tells Flutter: “something changed — run `build` again.” +- Good for **one screen** and **simple** data. + +--- + +## UI updates + +Rule of thumb: + +1. User taps → your code runs. +2. You change data (e.g. load todos from API). +3. You call **`setState`** (local) **or** notify listeners (Provider — see [provider.md](provider.md)). +4. Flutter **rebuilds** → user sees new UI. + +--- + +## When local state is not enough + +If **many widgets** or **many screens** need the **same** data, passing variables through every constructor gets messy. That’s **prop drilling** — see [prop-drilling.md](prop-drilling.md). + +--- + +## Next + +[provider.md](provider.md) for sharing state app-wide in a clean way.