본문 바로가기

[플러터] 다른 앱에서 플러터 앱으로 텍스트 공유 받기-앱 중복 실행 해결법 포함

ironwhale 2023. 12. 9.

우리가 사용하는 앱들을 서로 공유기능을 통해 글자나 사진 등을 주고 받을 수 있습니다.  안드로이드에서는 아래와 같은 아이콘을 클릭하면 다른앱으로 텍스트나 사진 등을 보낼 수 있는 공유기능을 사용 할수 있습니다. 현재 저는 마음의 글이라는 문장 수집 앱을 만들어 사용하고 있는데 밀리의 서재에서 책을 보다가 또는 인터넷에서 좋은 글을 발견하면 복사를 해서 일일히 붙여 넣었는데 공유 기능을 사용해서 자동으로 붙여넣기가 되도록 앱을 업데이트 하였습니다. 

공유아이콘
공유 아이콘

그래서 이번 포스팅에서는 receive_sharing_intent 패키지를 사용하여 공유 기능을 추가하는 법을 정리해보도록 하겠습니다. 그리고 이번에 공유기능 사용 시 앱이 계속 새로 실행되어 같은 앱이 공유 받을 때 마다 2개, 3개 이렇게 막 실행되는 것도 해결하는 법도 알아보겠습니다. 

 

플러터 공유기능 사용법

 


구현 순서

1. 패키지 설치

2. AndroidManifest.xml 설정

3. 플러터 코드 구현


1.  receive_sharing_intent 패키지 설치

처음에 receive_sharing_intent 패키지가 가장 예제도 많고 사람들이 많이 사용했지만 마지막 업데이트가 2년 전에 이루어져서 다른 패키지를 사용해 보려다가 결국 receive_sharing_intent 패키지 사용하게 되었습니다. 그런데 아무래도 2년전에 마지막 업데이트가 된만큼 최신 플러터 버전에서는 실행이 안될 수도 있어 다음과 같이 pubspec.yaml 파일로 하셔야 합니다. 

 

해당 패키지의 깃허브를 보니 어느 친절하신 분이 코드를 수정하신듯합니다. 

receive_sharing_intent:
  git:
    url: https://github.com/AyushmanG26/receive_sharing_intent.git
    ref: abedc3d6d5b80df396e9c300fd1a052cbab08827

2.  AndroidManifest.xml 설정

다른 앱에서 공유를 받기 위해서는 android/app/src/main/AndroidManifest.xml 파일을 수정 해야 합니다. 

공유 시 앱 중복 실행 문제 해결법

launchMode를 singleTask로 변경해야 공유 받을때마다 앱이 실행되어 같은 앱이 여러개가 실행되지 않습니다. 

android:launchMode="singleTask"

AndroidManifest.xml에 추가 코드

저는 우선 텍스트만 받아 올것 이기 때문에 activity사이에 아래 코드만 넣었습니다.

<intent-filter>
    <action android:name="android.intent.action.SEND" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:mimeType="text/*" />
</intent-filter>

전체 코드 :AndroidManifest.xml

<activity
    android:name=".MainActivity"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
    android:exported="true"
    android:hardwareAccelerated="true"
    android:launchMode="singleTask"
    android:theme="@style/LaunchTheme"
    android:windowSoftInputMode="adjustResize">
    <!-- Specifies an Android theme to apply to this Activity as soon as
         the Android process has started. This theme is visible to the user
         while the Flutter UI initializes. After that, this theme continues
         to determine the Window background behind the Flutter UI. -->
    <meta-data
        android:name="io.flutter.embedding.android.NormalTheme"
        android:resource="@style/NormalTheme" />

    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/*" />
    </intent-filter>
</activity>

 


3.  플러터 코드 구현

일단 크롬이나 밀리의 서재 등에서 글을 공유 기능으로 제가 만든 플러터앱으로 보낼때 해당 로직을 어디에 작성해야 하는지에 대한 고민이 있었습니다. 제가 만든 앱에 경우 외부에서 텍스트를 공유 받아 작성하는 페이지로 보내야 했기 때문에 이런 로직을 어디에 구현해야 제가 원하는 기능을 수행할 수 있을까에 대해 고민하였습니다. 해답은 앱이 실행되고 처음 보게 되는 스크린에 해당 로직을 구현하면 되었습니다.  

 - 앱이 백그라운드에서 실행 중일 때 

앱이 백그라운드에서 실행 중일 때 공유를 받으면 ReceiveSharingIntent.getTextStream() 로직이 실행 됩니다. 

 - 앱이 실행 중이 아닐때 

앱이 백그라운드에서 실행되고 있지 않으면 ReceiveSharingIntent.getInitialText() 로직이 실행됩니다. 

 

다란 백그라운에서 실행되던, 앱을 새로 시작해야 되던 안에서 실행되는 세부적인 로직은 똑같기 때문에 intentLogic() 메소드를 만들어 중복 코드를 줄였습니다. 

 

외부에서 값이 들어오는 부분 코드

late StreamSubscription _intentDataStreamSubscription;

@override
void dispose() {
  // TODO: implement dispose
  super.dispose();
  _intentDataStreamSubscription.cancel();
}

@override
void initState() {
  // TODO: implement initState
  super.initState();

  // For sharing or opening URLs/text.
  _intentDataStreamSubscription =
      ReceiveSharingIntent.getTextStream().listen((String? value) {
    logger.d("+++++++++++listen+++++++");
    if (value != null) {
      intentLogic(value);
    }
  }, onError: (err) {
    logger.e("getLinkStream error: $err");
  });

  // If the app was terminated, then this will extract the shared data

  ReceiveSharingIntent.getInitialText().then((String? value) {
    logger.d("+++++++++++then+++++++");
    if (value != null) {
      logger.d(value);
      intentLogic(value);
    }
  });
}

 

세부적인 로직을 처리하기 위한 intentLogic

우선 네이트 뉴스 기사나 나무위키에서 문장을  긁어서 공유를 하게 되며 인터넷 기사 주소까지 받아오는게 디폴트 인거 같습니다. 저는 구지 출처가 되는 주소까지 저장하는것은 불필요 하다고 생각하여 정규표현식을 사용해서 인터넷 주소 이전 부분만 받아오는 로직을 사용했고, 정규 표현식은 어려워서 ChatGPT를 활용하여 정규 표현식을 만들었습니다. 

네이트 뉴스에서 받아온 텍스트 값

 "인터뷰에서 알 사우드 왕자는 월드컵 여름 개최 가능성에 대해 "아직 확실하지는 않지만 겨울에 여는 선택지와 고민하고 있다"고 답했다.
영국 언론 '미러' 역시 "연중 가장 더운 기간일 때 사우디 평균 기온은 40도까지 치솟고 최고 50도까지 도달한다"며 사우디의 여름 개최는 매우 어려운 선택이라고 전했다. BBC 또한 이러한 점을 짚으며 여름 개최의 실질적 가능성을" 
https://sports.news.nate.com/view/20231208n30047#:~:text=%EC%9D%B8%ED%84%B0%EB%B7%B0%EC%97%90%EC%84%9C%20%EC%95%8C%20%EC%82%AC%EC%9A%B0%EB%93%9C%20%EC%99%95%EC%9E%90%EB%8A%94%20%EC%9B%94%EB%93%9C%EC%BB%B5%20%EC%97%AC%EB%A6%84%20%EA%B0%9C%EC%B5%9C%20%EA%B0%80%EB%8A%A5%EC%84%B1%EC%97%90%20%EB%8C%80%ED%95%B4%20%22%EC%95%84%EC%A7%81%20%ED%99%95%EC%8B%A4%ED%95%98%EC%A7%80%EB%8A%94%20%EC%95%8A%EC%A7%80%EB%A7%8C%20%EA%B2%A8%EC%9A%B8%EC%97%90%20%EC%97%AC%EB%8A%94%20%EC%84%A0%ED%83%9D%EC%A7%80%EC%99%80%20%EA%B3%A0%EB%AF%BC%ED%95%98%EA%B3%A0%20%EC%9E%88%EB%8B%A4%22%EA%B3%A0%20%EB%8B%B5%ED%96%88%EB%8B%A4.%0A%0A%EC%98%81%EA%B5%AD%20%EC%96%B8%EB%A1%A0%20%27%EB%AF%B8%EB%9F%AC%27%20%EC%97%AD%EC%8B%9C%20%22%EC%97%B0%EC%A4%91%20%EA%B0%80%EC%9E%A5%20%EB%8D%94%EC%9A%B4%20%EA%B8%B0%EA%B0%84%EC%9D%BC%20%EB%95%8C%20%EC%82%AC%EC%9A%B0%EB%94%94%20%ED%8F%89%EA%B7%A0%20%EA%B8%B0%EC%98%A8%EC%9D%80%2040%EB%8F%84%EA%B9%8C%EC%A7%80%20%EC%B9%98%EC%86%9F%EA%B3%A0%20%EC%B5%9C%EA%B3%A0%2050%EB%8F%84%EA%B9%8C%EC%A7%80%20%EB%8F%84%EB

 

정규표현식으로 원하는 부분만 추출하기

파이썬을 공부할 때도 느낀거지만 정규표현식은 한번 제대로 공부를 해야 할 필요가 있는거 같습니다. 정말 텍스트의 원하는 패턴을 추출하는데는 강력한 기능을 제공합니다. 

 

저는 그동안 모든 문자를 가지고 올때 .*을 사용했는데 이번에 챗GPT를 통해 [\s\S]로 새롭게 알게되었습니다. 둘의 차이는 엔터를 포함하는지 여부 입니다. 다음에 기회가 되면 정규표현식을 공부해서 정리해보도록 하겠습니다. 

정규표현식으로  기사 내용만 가져오고 작성하는 페이지로 가는 로직

RegExp regex = RegExp(r'([\s\S]+?)https?://\S+');
Match? match = regex.firstMatch(inputText);
if (match != null) {
  String extractedText = match.group(1)!;
  logger.d("++++++++++extractedText:$extractedText");
  Navigator.of(context).push(MaterialPageRoute(
      builder: (context) => WriteScreen(
            motto: motto.copyWith(contents: extractedText),
          )));
}

 


생각보다 간단해서 무언가 더 있어보이지만 진짜 단순한거 같습니다. 그래서 2년 전에 이미 패키지가 완성되서 업데이트가 안되는 것 일수 있는거 같습니다. 

댓글