Flutter 디지털 사이니지 앱 개발 가이드
1. 소개
1.1. 애플리케이션의 목적
본 문서는 Android 및 Android TV 플랫폼에서 작동하는 디지털 사이니지 클라이언트 애플리케이션을 Flutter를 통해 개발하는 과정을 안내한다. 이 애플리케이션은 서버 환경 및 PostgreSQL 데이터베이스와 통신하여 동적 콘텐츠(이미지 및 비디오)를 로컬에 다운로드하고, 이를 슬라이드쇼 형태로 재생하며, 주기적으로 콘텐츠를 갱신하는 것을 목표로 한다.
1.2. 핵심 기술 개요
개발에는 다음과 같은 핵심 기술이 사용된다:
- Flutter: Google에서 개발한 UI 툴킷으로, 단일 코드베이스를 사용하여 Android, iOS, 웹, 데스크톱 등 다양한 플랫폼을 위한 네이티브 애플리케이션을 구축할 수 있다. 본 프로젝트에서는 Android (일반 OS) 및 Android TV OS용 클라이언트 개발에 활용된다.
- Dart: Flutter 애플리케이션 개발에 사용되는 객체 지향 프로그래밍 언어이다.
- Android (Standard & TV OS): 애플리케이션이 배포될 대상 운영체제이다. 특히 Android TV OS의 특성을 고려한 UI/UX 및 네비게이션 구현이 중요하다.
- PostgreSQL: 백엔드 데이터베이스 시스템으로, 클라이언트 애플리케이션은 이 데이터베이스에 저장된 콘텐츠 메타데이터를 서버 API를 통해 조회하고 활용한다.
1.3. 가이드 구조
본 가이드는 프로젝트 설정부터 시작하여 각 기능별 구현 방법, 그리고 Android TV와 같은 특정 플랫폼 환경에서의 고려 사항을 단계별로 상세히 설명한다. 주요 내용은 다음과 같다:
- 핵심 애플리케이션 설정: Flutter 프로젝트 초기화 및 Android Manifest 파일의 필수 구성 요소를 다룬다.
- 기능 구현 가이드: 부팅 시 자동 실행, 전체 화면 모드, 사용자 인증, 콘텐츠 동기화 및 로컬 캐싱, 미디어 슬라이드쇼 등 요구되는 각 기능을 상세히 안내한다.
- Android TV 특정 고려 사항: Android TV 환경에서의 UI/UX 디자인 원칙, D-패드 네비게이션 및 포커스 관리 기법을 심층적으로 논의한다.
- 백엔드 고려 사항 (간략히): Flutter 클라이언트와 상호작용할 PHP/PostgreSQL 백엔드의 API 엔드포인트 및 데이터베이스 스키마에 대한 기본적인 사항을 언급한다.
- 결론 및 모범 사례 요약: 주요 과제와 해결책을 요약하고, 테스트 및 보안 관련 권장 사항을 제시한다.
2. 핵심 애플리케이션 설정
2.1. Flutter 프로젝트 초기화
Flutter 개발 환경(Flutter SDK, Android Studio 또는 VS Code 등의 IDE)이 사전에 구축되어 있다는 가정 하에, 다음 명령어를 사용하여 새로운 Flutter 프로젝트를 생성한다:
flutter create digital_signage_app
이 명령어는 digital_signage_app
이라는 이름의 Flutter 프로젝트 기본 구조를 생성한다.
2.2. 필수 Android Manifest 구성 (android/app/src/main/AndroidManifest.xml
)
애플리케이션의 올바른 작동과 특정 기능 활성화를 위해 AndroidManifest.xml
파일에 몇 가지 중요한 설정을 추가해야 한다.
인터넷 권한
서버 통신 및 파일 다운로드를 위해 필수적이다.
<uses-permission android:name="android.permission.INTERNET" />
이 권한은 애플리케이션이 네트워크에 접근하여 이미지/비디오 목록 및 실제 파일을 다운로드하는 데 기본적으로 요구된다.
저장 공간 권한
파일을 로컬 저장소에 다운로드하고 관리하기 위해서는 저장 공간 접근 권한이 필요하다. Android 버전별로 저장 공간 접근 방식에 차이가 있으므로 이를 고려해야 한다.
Android 10 (API 레벨 29) 미만 버전에서는 일반적으로 외부 저장소에 대한 광범위한 접근을 위해 다음 권한이 사용되었다:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Android 10 이상에서는 Scoped Storage 모델이 도입되어 앱이 외부 저장소에 접근하는 방식이 변경되었다. 앱별 디렉토리(app-specific directories)에 파일을 저장하는 경우, 해당 앱의 파일에 대해서는 명시적인 저장 공간 권한이 필요하지 않을 수 있다. path_provider
와 같은 플러그인은 이러한 앱별 디렉토리 경로를 얻는 데 사용된다.
그러나 flutter_downloader
와 같은 다운로드 관리 플러그인이 공유 저장 공간을 사용하거나 특정 기능을 위해 더 넓은 접근 권한을 요구하는 경우, 여전히 위 권한들이 필요할 수 있으며, 런타임에 사용자에게 권한을 요청해야 한다. permission_handler
패키지는 런타임 권한 요청을 처리하는 표준적인 방법이다.
2.3. Android TV 특정 Manifest 구성 (android/app/src/main/AndroidManifest.xml
)
Android TV에서 애플리케이션이 올바르게 인식되고 실행되도록 하려면 다음과 같은 TV 전용 설정을 Manifest 파일에 추가해야 한다.
Leanback Launcher 인텐트 필터
이 설정은 앱이 Android TV 홈 화면에 표시되도록 하는 데 필수적이다.
<application ...>
<activity ...>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
</application>
CATEGORY_LEANBACK_LAUNCHER
인텐트 필터는 앱이 TV용으로 활성화되었음을 식별하고 Google Play에서 TV 앱으로 인식하도록 한다.
Leanback 지원 선언
앱이 Leanback UI(TV 환경)용으로 설계되었음을 나타낸다.
<uses-feature android:name="android.software.leanback" android:required="false" />
android:required="false"
속성은 일반 Android와 Android TV 모두에서 작동하는 앱을 개발할 때 중요하다.
터치스크린 불필요 선언
TV와의 상호작용은 주로 D-패드를 통해 이루어진다.
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
이 설정은 앱이 터치 입력 없이 탐색 가능함을 나타낸다.
Android TV용 배너
TV 홈 화면에서 앱을 나타내는 데 사용되는 배너 이미지이다.
<application
...
android:banner="@drawable/your_tv_banner" >
...
</application>
your_tv_banner
는 res/drawable
디렉토리에 위치한 배너 이미지 파일의 이름으로 대체해야 한다.
3. 기능 구현 가이드
3.1. 부팅 시 자동 실행
애플리케이션(또는 콘텐츠 갱신을 위한 백그라운드 서비스)은 Android 장치(일반 또는 TV)가 부팅될 때 자동으로 시작되어야 한다.
핵심 Android 메커니즘
이는 android.intent.action.BOOT_COMPLETED
시스템 브로드캐스트를 수신하는 BroadcastReceiver
를 사용하여 구현된다.
권한
AndroidManifest.xml
에 android.permission.RECEIVE_BOOT_COMPLETED
권한이 필요하다.
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
BroadcastReceiver 구현 (Kotlin 예제)
package com.example.digital_signage_app // 앱의 패키지 이름으로 변경
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
class StartupReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
Log.d("StartupReceiver", "Boot completed, attempting to start service or activity.")
// 옵션 1: Flutter 액티비티 시작
// val activityIntent = Intent(context, MainActivity::class.java).apply {
// flags = Intent.FLAG_ACTIVITY_NEW_TASK
// }
// context.startActivity(activityIntent)
// 옵션 2: Flutter 백그라운드 서비스 시작 (플러그인 사용 권장)
// 예: flutter_background_service 또는 workmanager
// 특정 플러그인 문서를 참조하여 부팅 시 자동 시작 설정.
}
}
}
AndroidManifest.xml에 등록
<application ...>
<receiver android:name=".StartupReceiver" android:enabled="true" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
...
</application>
flutter_background_service
, android_alarm_manager_plus
, workmanager
와 같은 플러그인을 활용하면 부팅 시 자동 실행 및 백그라운드 작업 관리를 더 쉽게 구현할 수 있다.
기능 | flutter_background_service | android_alarm_manager_plus | workmanager | 비고 |
---|---|---|---|---|
부팅 시 설정 용이성 | autoStartOnBoot: true 설정으로 비교적 간편 |
RebootBroadcastReceiver 내장, 자동 재스케줄링 | 시스템 재부팅 간 작업 지속 지원 | 각 플러그인 문서 참조 필요 |
Flutter 엔진 초기화 | 플러그인 내부 처리 | 플러그인 내부 처리 | 플러그인 내부 처리 | 개발자가 직접 처리할 필요 없음 |
작업 유형 | 일회성, 주기적, 포그라운드 서비스 | 일회성, 정확한 시간의 주기적 알람 (Android 전용) | 지연 가능, 제약 조건 기반, 일회성, 주기적 작업 | android_alarm_manager_plus 는 정밀한 타이밍에 강점 |
iOS 지원 | 제한적 (Background Fetch 사용) | 지원 안 함 | 지원 (Background Fetch/Processing 사용) | iOS 백그라운드 실행은 제약이 많음 |
3.2. 전체 화면 모드
애플리케이션은 시스템 바(상태 바, 네비게이션 바)를 숨기고 전체 화면을 차지해야 한다. Flutter의 SystemChrome
API를 사용한다.
import 'package:flutter/services.dart';
void enableFullScreenMode() {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
// 또는 이전 방식:
// SystemChrome.setEnabledSystemUIOverlays([]); // 모든 오버레이 숨김
}
SystemUiMode.immersiveSticky
는 사용자가 화면 가장자리에서 스와이프하면 시스템 바가 잠시 나타났다가 다시 사라지는 지속적인 전체 화면 모드이다. 일반적으로 main()
함수 또는 메인 앱 위젯의 initState
에서 호출된다.
3.3. 사용자 인증
3.3.1. 로그인 UI 구축
표준 Flutter 위젯(Scaffold
, Column
, TextField
, ElevatedButton
, Form
)을 사용하여 사용자 이름/ID 및 비밀번호 입력 필드와 로그인 버튼으로 구성된 UI를 만든다. TextEditingController
로 입력 값을 가져오고 유효성 검사를 구현한다.
3.3.2. 로그인 자격 증명/토큰 보안 저장
성공적인 로그인 후 서버는 세션 토큰(예: JWT)을 반환해야 한다. flutter_secure_storage
패키지를 사용하여 이 토큰을 안전하게 저장한다.
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
final storage = FlutterSecureStorage();
// 토큰 저장
await storage.write(key: 'auth_token', value: receivedToken);
// 토큰 읽기
String? token = await storage.read(key: 'auth_token');
이 패키지는 iOS의 Keychain과 Android의 KeyStore 기반 AES 암호화를 사용한다.
3.3.3. 자동 로그인 로직 구현
앱 시작 시 flutter_secure_storage
에 저장된 토큰을 확인한다.
String? token = await storage.read(key: 'auth_token');
if (token != null && token.isNotEmpty) {
// 서버로 토큰 유효성 검사 (선택 사항이지만 권장)
// 유효하면 메인 앱 화면으로 이동.
} else {
// 로그인 화면으로 이동.
}
만료되거나 유효하지 않은 토큰을 정상적으로 처리하여 로그인 화면으로 리디렉션해야 한다.
3.4. 콘텐츠 가져오기 및 로컬 캐싱 (이미지/비디오)
3.4.1. 백엔드로 HTTP 요청하기
dio
또는 http
패키지를 사용하여 백엔드 API에 파일 메타데이터(URL, 파일 이름, 버전 등) 목록을 요청한다. 인증 토큰을 요청 헤더에 포함시킨다.
// dio 패키지 사용 예시
import 'package:dio/dio.dart';
Future fetchFileList() async {
Dio dio = Dio();
String? token = await storage.read(key: 'auth_token'); // flutter_secure_storage 사용
try {
Response response = await dio.get(
'YOUR_API_ENDPOINT/files',
options: Options(
headers: {
'Authorization': 'Bearer $token',
},
),
);
// response.data 처리 (파일 메타데이터 목록)
print(response.data);
} catch (e) {
print('Error fetching file list: $e');
}
}
3.4.2. 로컬 저장소로 파일 다운로드
flutter_downloader
패키지를 사용하여 파일을 백그라운드에서 다운로드하고, path_provider
를 사용하여 적절한 로컬 저장소 경로를 얻는다.
import 'package:path_provider/path_provider.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
Future downloadFile(String url, String fileName) async {
final directory = await getApplicationDocumentsDirectory();
final localPath = '${directory.path}/$fileName';
final taskId = await FlutterDownloader.enqueue(
url: url,
savedDir: directory.path,
fileName: fileName,
showNotification: true, // 알림 표시 여부
openFileFromNotification: true, // 알림 클릭 시 파일 열기 여부
);
}
// main.dart에 FlutterDownloader 초기화 필요
// void main() async {
// WidgetsFlutterBinding.ensureInitialized();
// await FlutterDownloader.initialize(
// debug: true // 디버그 로그 활성화
// );
// runApp(MyApp());
// }
flutter_downloader
사용 시 네이티브 설정(Android Manifest, iOS Info.plist)이 필요할 수 있다.
3.4.3. 주기적 새로고침 예약
workmanager
또는 android_alarm_manager_plus
(Android 전용) 패키지를 사용하여 30분마다 파일 목록을 가져오고 로컬 파일을 업데이트하는 백그라운드 작업을 예약한다.
// workmanager 사용 예시
import 'package:workmanager/workmanager.dart';
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) {
print("Native called background task: $task"); // 실제 콘텐츠 새로고침 로직 호출
// fetchFileList() 및 동기화 로직 수행
return Future.value(true);
});
}
void schedulePeriodicRefresh() {
Workmanager().initialize(
callbackDispatcher,
isInDebugMode: true,
);
Workmanager().registerPeriodicTask(
"contentRefreshTaskUniqueName", // 고유 이름
"contentRefreshTask", // 작업 이름
frequency: Duration(minutes: 30),
constraints: Constraints(
networkType: NetworkType.connected,
),
);
}
3.5. 선택적 파일 동기화
주기적 새로고침 시, 로컬 파일 목록과 서버 파일 목록(메타데이터에 버전/타임스탬프 포함)을 비교한다. 새 파일이나 수정된 파일만 다운로드하고, 서버 목록에 없는 로컬 파일은 삭제한다.
로직 개요:
- 서버에서 최신 파일 메타데이터 목록을 가져온다.
- 로컬에 저장된 파일 메타데이터 목록(또는 실제 파일 정보)을 가져온다.
- 두 목록을 비교하여 다음을 식별한다:
- 다운로드할 파일: 서버에는 있지만 로컬에는 없거나, 로컬 버전보다 최신인 파일.
- 삭제할 파일: 로컬에는 있지만 서버 목록에는 없는 파일.
- 식별된 파일들을 다운로드하거나 삭제한다.
파일 내용의 해시(예: MD5, SHA256)를 사용하면 타임스탬프보다 더 정확한 변경 감지가 가능하다.
3.6. 로컬 미디어 슬라이드쇼
다운로드한 이미지와 비디오를 자동으로 순환 재생한다. carousel_slider
(이미지/위젯 캐러셀) 및 video_player
(비디오 재생) 패키지를 사용할 수 있다.
혼합 재생 목록 관리:
- 로컬 파일 경로 및 유형(이미지/비디오) 목록을 유지한다.
PageView
또는AnimatedSwitcher
를 사용하여 현재 항목을 표시한다.PageView
의itemBuilder
는 파일 유형에 따라Image.file()
또는VideoPlayer
위젯을 반환한다.VideoPlayerController
의 수명 주기(초기화, 재생, 폐기)를 신중하게 관리한다.
자동 전환:
- 이미지:
Timer
를 사용하여 일정 시간 후 다음 항목으로 전환한다. - 비디오:
VideoPlayerController
의 리스너를 사용하여 재생 완료 시 다음 항목으로 전환한다.
AnimatedSwitcher
는 다른 유형의 위젯 간 전환(예: 이미지에서 비디오로)을 부드럽게 처리하는 데 유용할 수 있다.
4. Android TV 특정 고려 사항
4.1. TV용 UI/UX 모범 사례
- 10피트 UI: 멀리서 보는 것을 고려하여 큰 글꼴, 선명한 시각 자료, 높은 대비를 사용한다.
- 간단한 네비게이션: D-패드 네비게이션은 직관적이고 예측 가능해야 한다.
- 포커스 하이라이트: 현재 포커스된 요소를 명확하게 표시한다.
- 레이아웃: 가로 방향을 주로 사용하며, 콘텐츠 탐색에는 그리드 레이아웃이 일반적이다.
- 혼잡함 피하기: UI를 깔끔하게 유지하고 콘텐츠에 집중한다.
4.2. D-패드 네비게이션 및 포커스 관리
Flutter의 포커스 시스템 (FocusNode
, FocusScope
, FocusTraversalGroup
, FocusTraversalPolicy
)을 활용하여 D-패드 네비게이션을 구현한다.
핵심 개념
FocusNode
: UI에서 포커스 가능한 요소를 나타낸다.Shortcuts
및Actions
위젯: 키 이벤트(예: D-패드 선택)를Intent
및Action
에 매핑한다.RawKeyboardListener
: 원시 키 이벤트에 대한 하위 수준 액세스를 제공한다.
포커스 순서 관리, 포커스 하이라이트 사용자 정의, 포커스 디버깅이 중요하다. 디지털 사이니지 앱의 주 슬라이드쇼 뷰는 복잡한 D-패드 네비게이션이 필요 없을 수 있지만, 설정 화면 등에서는 필수적이다.
기법 | 핵심 Flutter 클래스/위젯 | 사용 사례 | 비고 |
---|---|---|---|
기본 포커스 | Focus , FocusNode |
위젯을 포커스 가능하게 만들기 | FocusNode 는 상태를 유지하고 프로그래밍 방식 제어를 허용한다. |
프로그래밍 방식 포커스 | FocusNode.requestFocus() , FocusScope.of(context).requestFocus() |
특정 위젯으로 포커스 동적 이동 | 버튼 클릭, 유효성 검사 실패 시 등에 사용된다. |
포커스 순서 | FocusTraversalGroup , FocusTraversalPolicy (예: OrderedTraversalPolicy , ReadingOrderTraversalPolicy ), FocusOrder |
D-패드 네비게이션 순서 정의 | TV UI에서 예측 가능한 네비게이션에 매우 중요하다. |
키 이벤트 처리 | Shortcuts , Actions , LogicalKeyboardKey , ActivateIntent , RawKeyboardListener |
D-패드 입력(선택, 방향키)에 응답 | Shortcuts /Actions 는 의미론적이며, RawKeyboardListener 는 하위 수준이다. |
시각적 하이라이트 | Focus.onFocusChange , FocusNode.hasFocus , FocusThemeData , MaterialStateProperty |
포커스된 위젯을 시각적으로 강조 | 사용자에게 현재 선택된 항목을 명확하게 보여준다. |
5. 백엔드 고려 사항 (간략히 - PHP/PostgreSQL)
Flutter 애플리케이션의 기능은 백엔드 API와 밀접하게 연결되어 있으므로, 초기 단계에서 API 계약을 명확하게 정의하는 것이 중요하다.
API 엔드포인트 예시
/login
(POST): 자격 증명을 받아 인증 토큰을 반환한다./files
(GET, 인증됨): 로그인한 사용자에 대한 파일 메타데이터(URL, 이름, 버전/타임스탬프, 유형) 목록을 반환한다./download/<file_id>
(GET, 인증됨, 선택 사항): 파일을 제공하는 엔드포인트.
데이터베이스 (PostgreSQL) 스키마 예시
users
테이블:user_id
,username
,password_hash
등 사용자 정보를 저장한다.files
테이블:file_id
,user_id
,file_name
,file_url
,file_type
,version_timestamp
등 파일 정보를 저장한다.
PHP 백엔드는 PostgreSQL에 대한 인증을 처리하고 인증된 사용자를 기반으로 files
테이블을 쿼리한다. 파일 접근 보안은 백엔드의 책임이다.
6. 결론 및 모범 사례 요약
본 가이드는 Flutter를 사용하여 Android 및 Android TV용 디지털 사이니지 애플리케이션을 개발하는 데 필요한 핵심 기능과 고려 사항을 다루었다. 듀얼 플랫폼 개발의 주요 과제는 각 플랫폼의 특성을 이해하고 이에 맞는 UI/UX를 제공하는 것이다.
주요 모범 사례
- 플랫폼별 Manifest 구성: Android 및 Android TV 각각에 대해
AndroidManifest.xml
을 정확하게 구성한다. - 안전한 인증 및 토큰 관리:
flutter_secure_storage
를 사용하고 토큰 유효성을 검증한다. - 효율적인 콘텐츠 동기화: 주기적인 백그라운드 작업을 통해 선택적 동기화를 구현한다.
- Android TV 포커스 관리: Flutter의 포커스 시스템을 적극 활용하고 명확한 시각적 포커스를 제공한다.
- 철저한 테스트: 다양한 실제 장치에서 테스트하여 사용자 경험을 검증한다.
7. 참고 자료
본 가이드에서 언급된 기술 및 패키지에 대한 공식 문서 및 유용한 자료들입니다.
- Fetch data from the internet | Flutter
- Flutter Download File from URL: A Step-by-Step Guide - DhiWise
- Flutter_Downloader Tutorial: Implement File Downloads in Flutter ...
- Read and write files | Flutter
- How to Manage App Permissions in Flutter - F22 Labs
- permission_handler | Flutter package - Pub.dev
- Create and run a TV app | Android TV | Android Developers
- flutter_notification_listener - Dart API docs - Pub.dev (Note: This seems less relevant to boot completion directly, original document might have a context.)
- Auto-launch issue with Flutter application - Google Play Help
- Broadcasts overview | Background work | Android Developers
- Flutter Run Background Service Even After App is Killed - Medium
- android_alarm_manager_plus | Flutter package - Pub.dev
- Background processes | Flutter
- flutter_background_service | Flutter package - Pub.dev
- `flutter_background_service` stops only in release mode - GitHub Issue
- flutter_background_service example | Flutter package - Pub.dev
- Flutter App Development: Scheduling Background Task - Codeclever
- Mastering Background Tasks in Flutter with Workmanager - DhiWise
- How to Enable or Disable Full Screen Mode in Flutter App - Flutter Stuff
- Can you hide the status bar completely? (Android) - Reddit
- Layout tutorial - Flutter Documentation
- Flutter – Design Login Page UI | GeeksforGeeks
- flutter_secure_storage | Flutter package - pub.dev
- Storing Data in Secure Storage in Flutter | Blog | Digital.ai
- Flutter – Fetching List of Data From API Through Dio | GeeksforGeeks
- Flutter Dio Complete Guide - Dbestech
- Flutter – Fetch Data From REST APIs | GeeksforGeeks
- sync_client 1.2.2 | Flutter package - Pub.dev
- diffutil_dart | Dart package - pub.dev
- Dart - Compare List of Objects - Piyush Agarwal
- collection | Dart package - Pub.dev
- Set difference method - Dart API
- diffutil_dart example | Dart package - Pub.dev
- carousel_slider | Flutter package - Pub.dev
- Flutter Video Streaming and Player - VdoCipher
- Embed a video player in your Flutter application | Mux
- Layout | Flutter
- How to play video and image in a list Flutter - Stack Overflow
- Flutter: How to create a slider with images and videos - Stack Overflow
- Build an Image/Video Slider in Flutter - Dev.to
- AnimatedSwitcher class - Flutter API
- Awesome Flutter Carousel in 7 Minutes - YouTube (Original link might be dead/redirected)
- AnimatedSwitcher (Flutter Widget of the Week) - YouTube (Original link might be dead/redirected)
- material library - Flutter API
- Animation and motion widgets - Flutter Documentation
- Smart TV App Development Best Practices - EITBiz
- TV navigation - Android Developers
- FocusHighlightStrategy enum - Flutter API
- FocusHighlightMode enum - Flutter API
- Understanding focus - Flutter Documentation
- Focus class - Flutter API
- hasFocus property - FocusNode class - Flutter API
- Focus and text fields | Flutter
- skipTraversal property - FocusNode class - Flutter API
- descendantsAreFocusable property - FocusNode class - Flutter API
- canRequestFocus property - FocusNode class - Flutter API
- focusNode property - Focus class - Flutter API
- debugLabel property - FocusNode class - Flutter API
- Actions and Shortcuts - Flutter Documentation
- requestFocus method - FocusScope class - Flutter API
- setFirstFocus method - FocusScopeNode class - Flutter API
- autofocus method - FocusScopeNode class - Flutter API
- focusedChild property - FocusScope class - Flutter API
- FocusTraversalGroup class - Flutter API
- FocusOrder class - Flutter API
- FocusTraversalPolicy class - Flutter API
- OrderedTraversalPolicy class - Flutter API
- WidgetOrderTraversalPolicy class - Flutter API
- ReadingOrderTraversalPolicy class - Flutter API
- FocusableActionDetector class - Flutter API
- Shortcuts class - Flutter API
- CallbackShortcuts class - Flutter API
- Intent class - Flutter API
- Actions class - Flutter API
- HardwareKeyboard class - Flutter API
- LogicalKeyboardKey class - Flutter API
- onKey property - RawKeyboardListener class - Flutter API
- keyboard property - ServicesBinding mixin - Flutter API
- autofocus property - Focus class - Flutter API
- RequestFocusAction class - Flutter API
- descendantsAreFocusable property - FocusableActionDetector - Flutter API
- onFocusChange property - Focus class - Flutter API
- FocusHighlightBehavior enum - Flutter API
- overlayColor property - FocusThemeData - Flutter API
- MaterialStateProperty class - Flutter API
- addHighlightStrategy method - FocusManager - Flutter API
- paint method - FocusHighlightStrategy - Flutter API
- automatic property - FocusHighlightStrategy - Flutter API
- onShowFocusHighlight property - FocusableActionDetector - Flutter API