> ## Content Index
> Fetch the complete content index at: https://arkar.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# Day 26: Assets, SVGs & Custom Fonts
- URL: https://arkar.dev/day-26-assets-svgs-custom-fonts/
- Published: 2026-11-06T02:30:00.000Z
- Updated: 2026-11-06T02:30:00.000Z
- Description: Give Pixly its own look: register images, SVGs and fonts in pubspec.yaml, render a crisp SVG logo with flutter_svg, serve sharp PNGs with resolution-aware 2.0x/3.0x variants, and bundle a custom font.
- Author: Arkar Min Tun
- Tags: 100 days of flutter, flutter

မနေ့က Day 25 မှာ Pixly project ကို စတင်ပြီး Material 3 theme ကို တည်ဆောက်ခဲ့ပြီးပါပြီ။ ဒီနေ့မှာတော့ app ကို ကိုယ်ပိုင် identity ရှိလာအောင် logo, illustration, ပုံတွေ, font တွေ ထည့်သွင်းကြပါမယ်။ App တခုမှာ ပါတဲ့ ပုံ၊ icon၊ font၊ JSON file စတဲ့ app နဲ့အတူ တွဲပြီး install လုပ်သွားမယ့် file တွေကို Flutter မှာ **assets** လို့ ခေါ်ပါတယ်။

ဒီနေ့ ပြီးသွားရင် Pixly ရဲ့ AppBar မှာ SVG logo ပေါ်လာမယ်၊ ဓာတ်ပုံ မရှိသေးတဲ့ အချိန်မှာ ပြမယ့် empty state illustration ရလာမယ်၊ app တခုလုံးလဲ Poppins font နဲ့ ပြောင်းသွားမှာ ဖြစ်ပါတယ်။

## pubspec.yaml - Assets ရဲ့ Gatekeeper

Flutter က project folder ထဲမှာ ရှိတဲ့ file တွေကို အလိုအလျောက် app ထဲ ထည့်မပေးပါဘူး။ `pubspec.yaml` ထဲမှာ ကြေငြာထားတဲ့ asset တွေကိုပဲ app နဲ့အတူ bundle လုပ်ပေးတာ ဖြစ်ပါတယ်။ ပထမဆုံး project root မှာ folder တွေ ဖန်တီးလိုက်ပါမယ်။

```
pixly/
├── assets/
│   ├── fonts/
│   │   ├── Poppins-Regular.ttf
│   │   ├── Poppins-SemiBold.ttf
│   │   ├── Poppins-Bold.ttf
│   │   └── OFL.txt
│   ├── images/
│   │   ├── placeholder.png
│   │   ├── 2.0x/placeholder.png
│   │   └── 3.0x/placeholder.png
│   └── svg/
│       ├── logo.svg
│       └── empty_photos.svg
├── lib/
└── pubspec.yaml
```

ပြီးရင် `pubspec.yaml` ရဲ့ `flutter:` section အောက်မှာ ကြေငြာပေးပါမယ်။

```yaml
flutter:
  uses-material-design: true

  assets:
    - assets/images/
    - assets/svg/
    - assets/fonts/OFL.txt

  fonts:
    - family: Poppins
      fonts:
        - asset: assets/fonts/Poppins-Regular.ttf
        - asset: assets/fonts/Poppins-SemiBold.ttf
          weight: 600
        - asset: assets/fonts/Poppins-Bold.ttf
          weight: 700
```

`assets/images/` ဆိုပြီး နောက်ဆုံးမှာ `/` နဲ့ ရေးလိုက်တာက အဲ့ဒီ folder ထဲမှာ **တိုက်ရိုက်** ရှိတဲ့ file တွေ အကုန်လုံးကို ထည့်ပါဆိုတဲ့ အဓိပ္ပာယ်ပါ။ file တခုချင်းစီကို စာရင်းလိုက် ရေးနေစရာ မလိုတော့ပါဘူး။ သတိထားရမှာက sub folder တွေကိုတော့ အလိုအလျောက် မထည့်ပေးပါဘူး။ ဥပမာ `assets/images/icons/` ဆိုတဲ့ folder ထပ်ရှိရင် အဲ့ဒါကို သီးသန့် တကြောင်း ထပ်ရေးပေးရပါမယ်။ (ခြွင်းချက်ကတော့ အောက်မှာ ပြောမယ့် `2.0x`, `3.0x` folder တွေပါ။)

> ⚠️ YAML က indentation (space) ကို အရမ်း အလေးထားပါတယ်။ `assets:` နဲ့ `fonts:` က `flutter:` အောက်မှာ space ၂ ချက်၊ list item တွေက space ၄ ချက် ဖြစ်ရပါမယ်။ space မှားရင် `flutter pub get` လုပ်တဲ့အခါ error တက်ပါလိမ့်မယ်။

`pubspec.yaml` ပြင်ပြီးတိုင်း `fvm flutter pub get` run ပေးရပါမယ်။ Asset အသစ်တွေ ထည့်ပြီးရင် hot reload နဲ့ အမြဲ ပါမလာနိုင်တဲ့အတွက် app ကို stop လုပ်ပြီး အစကနေ ပြန် run ပေးတာ အသေချာဆုံးပါ။

### Asset Paths ကို တနေရာတည်းမှာ စုထားခြင်း

```dart
// lib/config/assets.dart
/// All asset paths in one place, so a typo is a compile error, not a crash.
class AppAssets {
  static const String logo = 'assets/svg/logo.svg';
  static const String emptyPhotos = 'assets/svg/empty_photos.svg';
  static const String placeholder = 'assets/images/placeholder.png';
}
```

Asset path တွေကို string အနေနဲ့ နေရာတိုင်းမှာ တိုက်ရိုက် ရေးမယ်ဆိုရင် စာလုံးတလုံး မှားတာနဲ့ compile တုန်းက ဘာမှ မပြဘဲ run နေတုန်းမှ "Unable to load asset" ဆိုပြီး error တက်ပါလိမ့်မယ်။ `AppAssets` class ထဲမှာ စုထားလိုက်ရင် `AppAssets.logoo` လို member နာမည် မှားရင် compile error ပြမှာ ဖြစ်ပြီး file နာမည်ပြောင်းချင်ရင်လဲ တနေရာတည်းမှာ ပြင်ရုံပါပဲ။

ဒါပေမယ့် `AppAssets` ထဲက path string ကိုယ်တိုင် မှားနေရင်တော့ compiler က မသိပါဘူး။ ဒါကြောင့် Pixly project ထဲမှာ path တွေ အကုန်လုံး တကယ် load လို့ရလားဆိုတာ စစ်ပေးတဲ့ test လေးတခု ထည့်ထားပါတယ်။ (Testing အကြောင်းကိုတော့ Day 86 မှာ အသေးစိတ် ပြောပါမယ်။)

```dart
// test/assets_test.dart
test('every AppAssets path is bundled', () async {
  for (final path in [
    AppAssets.logo,
    AppAssets.emptyPhotos,
    AppAssets.placeholder,
  ]) {
    final data = await rootBundle.load(path);
    expect(data.lengthInBytes, greaterThan(0), reason: path);
  }
});
```

## PNG vs SVG

App ထဲမှာ သုံးတဲ့ ပုံ format ၂ မျိုးကို နှိုင်းယှဥ်ကြည့်ရအောင်ပါ။

|                     | PNG / JPG                                       | SVG                                                 |
| ------------------- | ----------------------------------------------- | --------------------------------------------------- |
| ပုံသဏ္ဍာန်          | Pixel (အစက်) တွေနဲ့ ဖွဲ့စည်းထားတဲ့ raster image | Shape, line, color တွေကို ဖော်ပြထားတဲ့ vector (XML) |
| ချဲ့လိုက်ရင်        | ဝါးသွား၊ အစက်တွေ မြင်လာ                         | ဘယ်လောက်ချဲ့ချဲ့ ကြည်လင်                            |
| သင့်တော်တဲ့ နေရာ    | ဓာတ်ပုံ၊ အရောင်အသွေး ရှုပ်ထွေးတဲ့ ပုံ           | Logo, icon, illustration                            |
| Flutter မှာ သုံးပုံ | Image.asset() built-in ပါပြီးသား                | flutter\_svg package လိုအပ်                         |

Pixly ရဲ့ logo နဲ့ empty state illustration တွေက ရိုးရှင်းတဲ့ shape တွေပဲ ပါတဲ့အတွက် SVG နဲ့ လုပ်ပါမယ်။ Pexels က လာမယ့် ဓာတ်ပုံတွေကတော့ JPG တွေ ဖြစ်ပါတယ်။

## SVG ကို flutter\_svg နဲ့ ပြခြင်း

Flutter မှာ SVG ကို ပြဖို့ built-in widget မပါတဲ့အတွက် `flutter_svg` package ကို ထည့်ပေးရပါမယ်။

```bash
fvm flutter pub add flutter_svg
```

```dart
SvgPicture.asset(AppAssets.logo, width: 32, height: 32)
```

`SvgPicture.asset` ကို `Image.asset` လိုပဲ သုံးလို့ ရပါတယ်။ ဒီ logo ကို AppBar ရဲ့ title မှာ app name နဲ့ ဘေးချင်းကပ် ထည့်လိုက်ပါမယ်။ Day 25 က theme demo အတွက် ယာယီ ရေးထားခဲ့တဲ့ `HomeScreen` ကိုတော့ အောက်က code နဲ့ အစားထိုးလိုက်ပါမယ်။ (`EmptyState` widget ကိုတော့ ဆက်ပြီး ရေးသွားပါမယ်။)

```dart
// lib/screens/home_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:pixly/config/assets.dart';
import 'package:pixly/config/constants.dart';
import 'package:pixly/widgets/empty_state.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Row(
          spacing: 12,
          children: [
            SvgPicture.asset(AppAssets.logo, width: 32, height: 32),
            const Text(AppConstants.appName),
          ],
        ),
      ),
      body: EmptyState(
        title: 'No photos yet',
        message: 'Beautiful photos from Pexels will show up here soon.',
        action: Image.asset(AppAssets.placeholder, width: 48, height: 48),
      ),
    );
  }
}
```

Day 21 မှာ ပြောခဲ့တဲ့ `Row` ရဲ့ `spacing` parameter ကို သုံးပြီး logo နဲ့ စာကြား နေရာ 12 ခြားထားပါတယ်။

> 💡 `flutter_svg` က CSS ကို အပြည့်အဝ support မလုပ်ပါဘူး။ Design tool တွေကနေ SVG export လုပ်တဲ့အခါ style တွေကို inline CSS အနေနဲ့ မဟုတ်ဘဲ **Presentation Attributes** အနေနဲ့ export လုပ်ဖို့ flutter\_svg ရဲ့ documentation မှာ အကြံပြုထားပါတယ်။ SVG တခု မှန်မှန်ကန်ကန် မပေါ်ရင် ဒီ setting ကို အရင်ဆုံး စစ်ကြည့်ပါ။

### Reusable EmptyState Widget

ဓာတ်ပုံ မရှိသေးတဲ့အခါ၊ search ရလဒ် မတွေ့တဲ့အခါ၊ bookmark မရှိသေးတဲ့အခါ စတဲ့ နေရာ အများကြီးမှာ "ဘာမှ မရှိသေးပါဘူး" ဆိုတဲ့ screen တွေ ပြဖို့ လိုလာမှာပါ။ ဒါကြောင့် Day 16 မှာ လေ့လာခဲ့တဲ့ StatelessWidget နဲ့ ပြန်သုံးလို့ရတဲ့ widget တခု ဆောက်ထားလိုက်ပါမယ်။

```dart
// lib/widgets/empty_state.dart
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:pixly/config/assets.dart';

class EmptyState extends StatelessWidget {
  const EmptyState({
    super.key,
    required this.title,
    required this.message,
    this.action,
  });

  final String title;
  final String message;
  final Widget? action;

  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;

    return Center(
      child: Padding(
        padding: const EdgeInsets.all(32),
        child: Column(
          mainAxisSize: .min,
          children: [
            SvgPicture.asset(AppAssets.emptyPhotos, width: 180),
            const SizedBox(height: 24),
            Text(title, style: textTheme.titleLarge, textAlign: .center),
            const SizedBox(height: 8),
            Text(message, style: textTheme.bodyMedium, textAlign: .center),
            if (action != null) ...[const SizedBox(height: 24), action!],
          ],
        ),
      ),
    );
  }
}
```

`action` ကို optional (`Widget?`) ထားပြီး Day 6 မှာ လေ့လာခဲ့တဲ့ collection `if` နဲ့ spread operator (`...`) ကို သုံးပြီး `action` ပါမှ button လိုမျိုး widget ကို ပြပေးတာ ဖြစ်ပါတယ်။ နောက်ရက်တွေမှာ "Retry" button လိုမျိုးတွေ ဒီနေရာမှာ ထည့်သုံးသွားမှာပါ။

## Resolution-Aware Images (2.0x, 3.0x)

Phone တွေရဲ့ screen တွေက pixel density မတူကြပါဘူး။ Flutter က layout တွက်တဲ့အခါ logical pixel ကို သုံးပြီး logical pixel 1 ခုမှာ physical pixel ဘယ်နှခု ပါလဲဆိုတာကို **device pixel ratio** လို့ ခေါ်ပါတယ်။ ဥပမာ iPhone တော်တော်များများရဲ့ device pixel ratio က 3 ဖြစ်ပါတယ်။

PNG ပုံ 48×48 တခုကို width 48 logical pixel နဲ့ 3x screen မှာ ပြလိုက်ရင် physical pixel 144×144 နေရာကို ပုံအစက် 48×48 နဲ့ ချဲ့ဖြည့်ရတဲ့အတွက် ဝါးသွားပါလိမ့်မယ်။ ဒါကို ဖြေရှင်းဖို့ Flutter မှာ resolution variant တွေ ထည့်ပေးလို့ ရပါတယ်။

```
assets/images/placeholder.png        ← 48×48   (1.0x)
assets/images/2.0x/placeholder.png   ← 96×96   (2.0x)
assets/images/3.0x/placeholder.png   ← 144×144 (3.0x)
```

File နာမည် အတူတူနဲ့ `2.0x`, `3.0x` folder တွေထဲမှာ ပိုကြီးတဲ့ ပုံတွေ ထည့်ထားလိုက်ရုံပါပဲ။ code ထဲမှာတော့ ဘာမှ ပြောင်းစရာ မလိုပါဘူး။ `Image.asset('assets/images/placeholder.png')` လို့ပဲ ခေါ်ရင် Flutter က device ရဲ့ pixel ratio နဲ့ ကိုက်ညီတဲ့ variant ကို အလိုအလျောက် ရွေးပေးပါတယ်။ ratio တိတိကျကျ တူတဲ့ variant ရှိရင် အဲ့ဒါကို ယူပြီး မရှိရင်တော့ အနီးစပ်ဆုံးကို ယူပါတယ်။ ခြွင်းချက်အနေနဲ့ device pixel ratio 2 အောက် (ဥပမာ 1.25) screen တွေမှာတော့ ပုံ ဝါးတာ ပိုသိသာတဲ့အတွက် အနီးဆုံးကို မယူဘဲ ပိုကြီးတဲ့ variant (ဒီဥပမာမှာ `2.0x`) ကို ရွေးပေးပါတယ်။ `pubspec.yaml` မှာလဲ main file (ဒါမှမဟုတ် သူ့ folder) ကို ကြေငြာထားရင် variant folder တွေကို Flutter က အလိုအလျောက် ထည့်ပေးပါတယ်။

ဒီ demo ပုံလေးတွေထဲမှာ variant ကို ခွဲမြင်ရအောင် "1x", "2x", "3x" ဆိုပြီး စာလေးတွေ ရေးထားပါတယ်။ အောက်က screenshot ကို device pixel ratio 2 နဲ့ ရိုက်ထားတဲ့အတွက် `2.0x` variant ကို ရွေးသွားတာကို တွေ့ရမှာပါ။

## Custom Fonts

Font file တွေကို `assets/fonts/` ထဲ ထည့်ပြီး `pubspec.yaml` မှာ ကြေငြာထားပြီးပြီဆိုတော့ သုံးရုံပါပဲ။

- `family: Poppins` \- code ထဲမှာ ခေါ်သုံးမယ့် နာမည်
- `weight: 600`, `weight: 700` \- ဒီ file က ဘယ် font weight အတွက်လဲ ဆိုတာ။ `weight` မပါတဲ့ file က default (400, regular) ဖြစ်ပါတယ်။ ဒါကြောင့် `FontWeight.w600` သုံးရင် SemiBold file၊ `FontWeight.bold` (700) သုံးရင် Bold file ကို Flutter က ရွေးပေးပါတယ်။

App တခုလုံးမှာ ဒီ font ကို သုံးစေချင်တဲ့အတွက် Day 25 က `lib/config/theme.dart` ထဲက `AppTheme._build` မှာ တကြောင်းတည်း ထပ်ထည့်လိုက်ပါမယ်။

```dart
    final base = ThemeData(colorScheme: colorScheme, fontFamily: 'Poppins');
```

`ThemeData` မှာ `fontFamily` သတ်မှတ်လိုက်ရင် `textTheme` ထဲက text style အကုန်လုံး (title, body, label စတာတွေ) က Poppins ကို သုံးသွားမှာပါ။ Day 25 မှာ AppBar title ကို style အသစ် မဆောက်ဘဲ text theme ရဲ့ `titleLarge` ကနေပဲ bold ပြောင်းထားခဲ့တဲ့အတွက် AppBar title ကလဲ Poppins ကို အလိုအလျောက် ရသွားပါတယ်။ widget တခုတည်းမှာပဲ သုံးချင်ရင်တော့ `TextStyle(fontFamily: 'Poppins')` လို့ ရေးလို့ရပါတယ်။

### Font License

Font file တွေကို app ထဲ ထည့်ပြီး ဖြန့်ဝေမယ်ဆိုရင် license ကို စစ်ဖို့ လိုပါတယ်။ Poppins က SIL Open Font License (OFL) နဲ့ ဖြစ်တဲ့အတွက် app ထဲမှာ ထည့်သုံးလို့ ရပါတယ်။ ဒါပေမယ့် `OFL.txt` ကို folder ထဲမှာ ထားရုံနဲ့ app ထဲ မပါသွားပါဘူး။ အပေါ်က `pubspec.yaml` မှာ `assets/fonts/OFL.txt` ကို asset အနေနဲ့ ကြေငြာထားပြီး Flutter ရဲ့ `LicenseRegistry` မှာ မှတ်ပုံတင်ပေးမှ app ရဲ့ licenses page (`showLicensePage`) မှာ ပေါ်လာမှာ ဖြစ်ပါတယ်။

```dart
// lib/main.dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pixly/config/constants.dart';
import 'package:pixly/config/theme.dart';
import 'package:pixly/screens/home_screen.dart';

void main() {
  // Show the Poppins font license on Flutter's licenses page.
  LicenseRegistry.addLicense(() async* {
    final license = await rootBundle.loadString('assets/fonts/OFL.txt');
    yield LicenseEntryWithLineBreaks(['Poppins'], license);
  });

  runApp(const PixlyApp());
}
```

`addLicense` က license တွေကို `Stream` (Day 13) အနေနဲ့ ပြန်ပေးတဲ့ function ကို လက်ခံပါတယ်။ `async*` နဲ့ ရေးထားတဲ့ function က Stream တခုကို ပြန်ပေးပြီး `yield` လုပ်လိုက်တဲ့ value တွေက အဲ့ဒီ Stream ထဲကို တခုချင်း ဝင်သွားတာ ဖြစ်ပါတယ်။ Flutter က licenses page ကို ဖွင့်တဲ့အချိန်ကျမှ ဒီ function ကို ခေါ်ပြီး file ကို ဖတ်တာ ဖြစ်ပါတယ်။

`google_fonts` package ကို သုံးပြီးလဲ Google Fonts က font တွေကို လွယ်လွယ်ကူကူ သုံးလို့ ရပါတယ်။ သူက font file ကို asset ထဲမှာ ထည့်ထားရင် အဲ့ဒါကို အရင်သုံးပြီး မရှိရင် device ထဲမှာ cache လုပ်ထားတာကို ရှာ၊ အဲ့ဒါမှ မရှိရင်တော့ app run နေတုန်း internet ကနေ download လုပ်ပါတယ်။ Download လုပ်ရမယ့် အခြေအနေမှာ internet မရှိရင် ပထမဆုံးအကြိမ် ဖွင့်တဲ့အခါ font မပြောင်းသေးတာမျိုး ဖြစ်နိုင်ပါတယ်။ Pixly မှာတော့ font တမျိုးတည်းပဲ သုံးမှာ ဖြစ်တဲ့အတွက် package အပို မထည့်ဘဲ font file တွေကို app ထဲမှာ တိုက်ရိုက် bundle လုပ်လိုက်တာ ဖြစ်ပါတယ်။

## ရလဒ်

![Pixly with SVG logo, empty state and Poppins - light](https://arkar.dev/content/images/2026/10/100dof-day26_home_light.png)

Pixly with SVG logo, empty state and Poppins - light

![Pixly with SVG logo, empty state and Poppins - dark](https://arkar.dev/content/images/2026/10/100dof-day26_home_dark.png)

Pixly with SVG logo, empty state and Poppins - dark

AppBar မှာ SVG logo၊ အလယ်မှာ empty state illustration၊ စာတွေ အားလုံး Poppins font နဲ့ ပြောင်းသွားတာကို တွေ့ရမှာပါ။ Dark mode မှာလဲ logo နဲ့ illustration တွေ ကြည်ကြည်လင်လင် မြင်ရပါတယ်။

Examples

- Logo, icon, illustration → SVG + `flutter_svg`
- ဓာတ်ပုံ၊ screenshot → PNG/JPG + `2.0x`/`3.0x` variant
- Brand font → `assets/fonts/` \+ `pubspec.yaml` `fonts:` \+ `ThemeData(fontFamily: ...)`
- Asset path တွေ → `AppAssets` လို class တခုထဲမှာ စုထား

**Source code:** ဒီနေ့ရဲ့ code အပြည့်အစုံကို [GitHub](https://github.com/arkarmintun1/100-days-of-flutter/tree/main/day26/pixly?ref=arkar.dev) မှာ run ကြည့်လို့ ရပါတယ်။

---

ဒီနေ့ Day 26 အတွက်ကတော့ ဒီလောက်ပဲ ဖြစ်ပါတယ်။ အဆုံးထိ ဖတ်ပေးတဲ့အတွက် အများကြီး ကျေးဇူးတင်ပါတယ်။ နားမလည်တာတွေ အဆင်မပြေတာတွေ ရှိခဲ့ရင်လဲ အောက်မှာပေးထားတဲ့ discord server ထဲမှာ လာရောက်ဆွေးနွေးနိုင်ပါတယ်။ နောက်နေ့တွေမှာလဲ ဆက်လက်ပြီး sharing လုပ်သွားပေးသွားမှာ ဖြစ်တဲ့အတွက် subscribe လုပ်ထားဖို့ ဖိတ်ခေါ်ပါတယ်။

- Youtube: [https://www.youtube.com/@arkarmintun](https://www.youtube.com/@arkarmintun?ref=arkar.dev)
- Newsletter: <https://arkar.dev/>
- Discord: [https://discord.gg/3xUJ6k6dkH](https://discord.gg/3xUJ6k6dkH?ref=arkar.dev)