Flutter Deep Linking

Flutter에서 Deep Linking은 애플리케이션 내의 특정 화면이나 콘텐츠로 직접 이동할 수 있게 해주는 기능입니다. 이는 사용자가 애플리케이션을 열 때 특정 URL 또는 경로를 통해 특정 페이지나 상태로 직접 이동할 수 있도록 합니다. Deep Linking은 웹, 모바일 앱(앱 링크), 그리고 앱 내부의 링크로 구현할 수 있습니다. Flutter에서는 이러한 기능을 통해 애플리케이션의 탐색을 더 매끄럽고 직관적으로 만들 수 있습니다.

1. Deep Linking 기본 개념

  • Deep Linking: 애플리케이션의 특정 부분으로 사용자를 직접 안내할 수 있는 기능입니다. 예를 들어, 사용자가 이메일이나 푸시 알림에서 클릭하면 특정 상품 페이지로 바로 이동할 수 있습니다.
  • Universal Links / App Links: 모바일 앱에서 사용할 수 있는 고급 형태의 Deep Linking으로, 사용자가 링크를 클릭했을 때 앱이 설치되어 있으면 앱을 열고, 그렇지 않으면 웹 페이지를 열도록 설정할 수 있습니다.

2. Flutter에서 Deep Linking 구현

Flutter에서 Deep Linking을 구현하는 방법에는 여러 가지가 있으며, 주요 방법은 다음과 같습니다:

  1. 앱 내 Deep Linking (Flutter)
  2. 앱 링크와 웹 링크 (iOS와 Android)
  3. 기타 유용한 라이브러리와 패키지

2.1. 앱 내 Deep Linking (Flutter)

Flutter에서는 Navigator를 사용하여 앱 내에서 Deep Linking을 구현할 수 있습니다. 이를 위해 URL 기반 라우팅을 지원하는 패키지를 사용할 수 있습니다. 기본적으로 Flutter의 Navigator 2.0을 사용하여 URL 기반 내비게이션을 설정할 수 있습니다.

Navigator 2.0을 사용한 Deep Linking

Navigator 2.0을 사용하면 URL 기반 내비게이션을 구현할 수 있습니다. 이 접근 방식은 선언적 내비게이션을 지원하며, 웹과 데스크톱 애플리케이션에서 특히 유용합니다.

예제 코드:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final _routerDelegate = MyRouterDelegate();
  final _routeInformationParser = MyRouteInformationParser();

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerDelegate: _routerDelegate,
      routeInformationParser: _routeInformationParser,
    );
  }
}

class MyRouterDelegate extends RouterDelegate<RouteSettings>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<RouteSettings> {
  final GlobalKey<NavigatorState> navigatorKey;

  List<Page> _pages = [MaterialPage(child: HomePage())];

  MyRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>();

  @override
  RouteSettings get currentConfiguration => _pages.last.arguments as RouteSettings;

  @override
  Future<void> setNewRoutePath(RouteSettings configuration) async {
    if (configuration.name == '/') {
      _pages = [MaterialPage(child: HomePage())];
    } else if (configuration.name == '/details') {
      _pages.add(MaterialPage(child: DetailsPage()));
    }
    notifyListeners();
  }

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      pages: List.of(_pages),
      onPopPage: (route, result) {
        if (!route.didPop(result)) {
          return false;
        }
        _pages.removeLast();
        notifyListeners();
        return true;
      },
    );
  }
}

class MyRouteInformationParser extends RouteInformationParser<RouteSettings> {
  @override
  Future<RouteSettings> parseRouteInformation(RouteInformation routeInformation) async {
    final uri = Uri.parse(routeInformation.location!);
    if (uri.pathSegments.isEmpty) {
      return RouteSettings(name: '/');
    } else if (uri.pathSegments.length == 1 && uri.pathSegments[0] == 'details') {
      return RouteSettings(name: '/details');
    } else {
      return RouteSettings(name: '/');
    }
  }

  @override
  RouteInformation? restoreRouteInformation(RouteSettings configuration) {
    if (configuration.name == '/') {
      return RouteInformation(location: '/');
    } else if (configuration.name == '/details') {
      return RouteInformation(location: '/details');
    }
    return null;
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.of(context).pushNamed('/details');
          },
          child: Text('Go to Details Page'),
        ),
      ),
    );
  }
}

class DetailsPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Details Page'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.of(context).pop();
          },
          child: Text('Back to Home Page'),
        ),
      ),
    );
  }
}

2.2. 앱 링크와 웹 링크 설정

앱 링크와 웹 링크는 iOS와 Android에서 설정할 수 있습니다. 이를 통해 사용자가 웹 링크를 클릭하면 앱이 설치되어 있을 때 앱을 열도록 할 수 있습니다.

Android에서 App Links 설정
  1. AndroidManifest.xml에 인텐트 필터 추가
   <activity
       android:name=".MainActivity"
       android:launchMode="singleTop">
       <intent-filter>
           <action android:name="android.intent.action.VIEW" />
           <category android:name="android.intent.category.DEFAULT" />
           <category android:name="android.intent.category.BROWSABLE" />
           <data android:scheme="https" android:host="example.com" />
       </intent-filter>
   </activity>
  1. assetlinks.json 파일 생성 https://example.com/.well-known/assetlinks.json에 다음과 같은 JSON 파일을 배포합니다.
   [
     {
       "relation": ["delegate_permission/common.handle_all_urls"],
       "target": {
         "namespace": "android_app",
         "package_name": "com.example.app",
         "sha256_cert_fingerprints": ["YOUR_CERT_FINGERPRINT"]
       }
     }
   ]
iOS에서 Universal Links 설정
  1. Info.plist 파일 수정
   <key>CFBundleURLTypes</key>
   <array>
       <dict>
           <key>CFBundleURLSchemes</key>
           <array>
               <string>myapp</string>
           </array>
       </dict>
   </array>
   <key>AssociatedDomains</key>
   <array>
       <string>applinks:example.com</string>
   </array>
  1. apple-app-site-association 파일 생성 https://example.com/apple-app-site-association에 다음과 같은 JSON 파일을 배포합니다.
   {
     "applinks": {
       "apps": [],
       "details": [
         {
           "appID": "TEAM_ID.com.example.app",
           "paths": ["*"]
         }
       ]
     }
   }

2.3. 패키지와 라이브러리

uni_links 패키지: Flutter 애플리케이션에서 Deep Linking을 지원합니다.

설치 : pubspec.yaml에 추가합니다

dependencies:
  uni_links: ^0.5.1

사용

 import 'package:uni_links/uni_links.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    _initLinks();
  }

  Future<void> _initLinks() async {
    try {
      final initialLink = await getInitialLink();
      if (initialLink != null) {
        print('Initial link: $initialLink');
        // Handle the link
      }
      linkStream.listen((String? link) {
        print('Link: $link');
        // Handle the link
      });
    } catch (e) {
      print('Failed to get initial link');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Deep Linking')),
        body: Center(child: Text('Home Page')),
      ),
    );
  }
}

요약

  • Deep Linking은 애플리케이션 내의 특정 화면으로 직접 이동할 수 있게 해줍니다.
  • Flutter 내비게이션을 사용하여 URL 기반 라우팅을 구현할 수 있습니다.
  • 앱 링크와 웹 링크는 iOS와 Android에서 설정할 수 있습니다.
  • uni_links 패키지를 사용하여 Flutter 애플

Leave a Reply

Your email address will not be published. Required fields are marked *