bhagat-techind / flutter_sharing_intent

A flutter plugin to share file from other app to our app.
Apache License 2.0
48 stars 49 forks source link

App not launching with share intent in IOS #12

Closed akilanfidelis closed 1 year ago

akilanfidelis commented 1 year ago

I followed through steps in the readme.

Tested on

Version: flutter_sharing_intent: ^1.0.6

https://user-images.githubusercontent.com/83576211/223353147-ae59b543-31bc-4c45-9499-ab7eb4c3529d.mp4

krille-chan commented 1 year ago

Same here

Yotam124 commented 1 year ago

same

akilanfidelis commented 1 year ago

I tried with real device, it works there, did you check there ?

lxgogogo commented 1 year ago

Both the app and the extension had to have the same ios Deployment target in build settings. Remember to uninstall the old version after the modification.

csacchetti commented 1 year ago

I've the same problem. I've the same deployment target in build settings 14.0, but myApp don't open or not resume if it's in background. I try in simulator and real device, but the behavior don't change. My suspicion is that I have not properly understood how to modify AppDelegate. This is how I did it. Is this correct?

import UIKit
import Flutter
import flutter_sharing_intent

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {

    override func application(
      _ application: UIApplication,
      didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
      GeneratedPluginRegistrant.register(with: self)
      return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {

         let sharingIntent = SwiftFlutterSharingIntentPlugin.instance
         /// if the url is made from SwiftFlutterSharingIntentPlugin then handle it with plugin [SwiftFlutterSharingIntentPlugin]
         if sharingIntent.hasSameSchemePrefix(url: url) {
             return sharingIntent.application(app, open: url, options: options)
         }
        // Proceed url handling for other Flutter libraries like uni_links
             return super.application(app, open: url, options:options)
    }

}
csacchetti commented 1 year ago

https://github.com/bhagat-techind/flutter_sharing_intent/assets/46923349/7b8eb48e-95c8-43b3-9c01-4a9e446a65f3

This is the video showing my problem. The app does not open

csacchetti commented 1 year ago

I tried downloading the example app you made to see if I had done something wrong in configuring mine, but the result is the same. So it's not my problem.

I set the minimum Deployed to 14.0 because it is required by

let imageContentType = UTType.image.identifier; 

and others

I also noticed that the AppDelegate gives this error

Screenshot 2023-06-08 alle 21 45 46
GaelleJoubert commented 1 year ago

I have the same issue, Deployed target minimum also set to 14 to both share extension and Runner .. and I have the exact same behavior than @csacchetti video.

GaelleJoubert commented 1 year ago

I found the fix ! Have a look at https://github.com/bhagat-techind/flutter_sharing_intent/issues/9 this issue. I changed the "sharing file" to the same name in both places (like they said in the issue), and also add my groupid explicitely. The app open now.

GaelleJoubert commented 1 year ago

Now the app open, but it looks like it does not goes through the callback : FlutterSharingIntent.instance.getMediaStream().listen ...

csacchetti commented 1 year ago

@GaelleJoubert I have tried to change these two things in ShareViewController

let url = URL(string: "SharingMedia-\(hostAppBundleIdentifier)://dataUrl=\(sharedKey)#\(type)")

and in info.plist/Runner I put the same name SharingMedia and add AppGroupID with the explicit name of my group

<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>SharingMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
        </array>
    </dict>
</array>

<key>AppGroupId</key>
<string>group.com.xxxxxxxx.xxxxx</string>

but the app does not open

yangricardo commented 1 year ago

Same here

didiabel commented 1 year ago

in SharingViewController.swift change line "SharingMedia-\(hostAppBundleIdentifier)://dataUrl=\(sharedKey)#\(type)" to "ShareMedia-\(hostAppBundleIdentifier)://dataUrl=\(sharedKey)#\(type)"

this worked, opened the app for me.

Dominik-domin commented 1 year ago

@didiabel Hi, could you send your whole code of how you implemented this package? I'm trying to do everything you mentioned here guys but still my app does not open :(

aviataur commented 1 year ago

Now the app open, but it looks like it does not goes through the callback : FlutterSharingIntent.instance.getMediaStream().listen ...

Facing the same issue on iOS, works on Android perfectly for some reason

didiabel commented 1 year ago

@aviataur @Dominik-domin hey guys, check your xcode, make sure that you have added & selected the same group it should be something like: group.${bundleID}, and that minimum deployment for the Share Extension and Runner are the same, in my case 11.0.

aviataur commented 1 year ago

@aviataur @Dominik-domin hey guys, check your xcode, make sure that you have added & selected the same group it should be something like: group.${bundleID}, and that minimum deployment for the Share Extension and Runner are the same, in my case 11.0.

I did, both group and min deployment are same for Share extension as well as Runner.

didiabel commented 1 year ago

try changing your ShareViewController.swift (dont forget to change bundle id) to the following:

import UIKit
import Social
import MobileCoreServices
import Photos

class ShareViewController: SLComposeServiceViewController {
    // TODO: IMPORTANT: This should be your host app bundle identifier
    let hostAppBundleIdentifier = "com.your.app"
    let sharedKey = "ShareKey"
    var sharedMedia: [SharedMediaFile] = []
    var sharedText: [String] = []
    let imageContentType = kUTTypeImage as String
    let videoContentType = kUTTypeMovie as String
    let textContentType = kUTTypeText as String
    let urlContentType = kUTTypeURL as String
    let fileURLType = kUTTypeFileURL as String;

    override func isContentValid() -> Bool {
        return true
    }

    override func viewDidLoad() {
        super.viewDidLoad();
    }

    override func didSelectPost() {
          if let content = extensionContext!.inputItems[0] as? NSExtensionItem {
            if let contents = content.attachments {
                for (index, attachment) in (contents).enumerated() {
                    if attachment.hasItemConformingToTypeIdentifier(imageContentType) {
                        handleImages(content: content, attachment: attachment, index: index)
                    } else if attachment.hasItemConformingToTypeIdentifier(textContentType) {
                        handleText(content: content, attachment: attachment, index: index)
                    } else if attachment.hasItemConformingToTypeIdentifier(fileURLType) {
                        handleFiles(content: content, attachment: attachment, index: index)
                    } else if attachment.hasItemConformingToTypeIdentifier(urlContentType) {
                        handleUrl(content: content, attachment: attachment, index: index)
                    } else if attachment.hasItemConformingToTypeIdentifier(videoContentType) {
                        handleVideos(content: content, attachment: attachment, index: index)
                    }
                }
            }
        }
    }

    override func configurationItems() -> [Any]! {
        // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
        return []
    }

    private func handleText (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
        attachment.loadItem(forTypeIdentifier: textContentType, options: nil) { [weak self] data, error in

            if error == nil, let item = data as? String, let this = self {

                this.sharedText.append(item)

                // If this is the last item, save imagesData in userDefaults and redirect to host app
                if index == (content.attachments?.count)! - 1 {
                    let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
                    userDefaults?.set(this.sharedText, forKey: this.sharedKey)
                    userDefaults?.synchronize()
                    this.redirectToHostApp(type: .text)
                }

            } else {
                self?.dismissWithError()
            }
        }
    }

    private func handleUrl (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
        attachment.loadItem(forTypeIdentifier: urlContentType, options: nil) { [weak self] data, error in

            if error == nil, let item = data as? URL, let this = self {

                this.sharedText.append(item.absoluteString)

                // If this is the last item, save imagesData in userDefaults and redirect to host app
                if index == (content.attachments?.count)! - 1 {
                    let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
                    userDefaults?.set(this.sharedText, forKey: this.sharedKey)
                    userDefaults?.synchronize()
                    this.redirectToHostApp(type: .text)
                }

            } else {
                self?.dismissWithError()
            }
        }
    }

    private func handleImages (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
        attachment.loadItem(forTypeIdentifier: imageContentType, options: nil) { [weak self] data, error in

            if error == nil, let url = data as? URL, let this = self {

                // Always copy
                let fileName = this.getFileName(from: url, type: .image)
                let newPath = FileManager.default
                    .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")!
                    .appendingPathComponent(fileName)
                let copied = this.copyFile(at: url, to: newPath)
                if(copied) {
                    this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .image))
                }

                // If this is the last item, save imagesData in userDefaults and redirect to host app
                if index == (content.attachments?.count)! - 1 {
                    let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
                    userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey)
                    userDefaults?.synchronize()
                    this.redirectToHostApp(type: .media)
                }

            } else {
                 self?.dismissWithError()
            }
        }
    }

    private func handleVideos (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
        attachment.loadItem(forTypeIdentifier: videoContentType, options: nil) { [weak self] data, error in

            if error == nil, let url = data as? URL, let this = self {

                // Always copy
                let fileName = this.getFileName(from: url, type: .video)
                let newPath = FileManager.default
                    .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")!
                    .appendingPathComponent(fileName)
                let copied = this.copyFile(at: url, to: newPath)
                if(copied) {
                    guard let sharedFile = this.getSharedMediaFile(forVideo: newPath) else {
                        return
                    }
                    this.sharedMedia.append(sharedFile)
                }

                // If this is the last item, save imagesData in userDefaults and redirect to host app
                if index == (content.attachments?.count)! - 1 {
                    let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
                    userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey)
                    userDefaults?.synchronize()
                    this.redirectToHostApp(type: .media)
                }

            } else {
                 self?.dismissWithError()
            }
        }
    }

    private func handleFiles (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
        attachment.loadItem(forTypeIdentifier: fileURLType, options: nil) { [weak self] data, error in

            if error == nil, let url = data as? URL, let this = self {

                // Always copy
                let fileName = this.getFileName(from :url, type: .file)
                let newPath = FileManager.default
                    .containerURL(forSecurityApplicationGroupIdentifier: "group.\(this.hostAppBundleIdentifier)")!
                    .appendingPathComponent(fileName)
                let copied = this.copyFile(at: url, to: newPath)
                if (copied) {
                    this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .file))
                }

                if index == (content.attachments?.count)! - 1 {
                    let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
                    userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey)
                    userDefaults?.synchronize()
                    this.redirectToHostApp(type: .file)
                }

            } else {
                self?.dismissWithError()
            }
        }
    }

    private func dismissWithError() {
        print("[ERROR] Error loading data!")
        let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert)

        let action = UIAlertAction(title: "Error", style: .cancel) { _ in
            self.dismiss(animated: true, completion: nil)
        }

        alert.addAction(action)
        present(alert, animated: true, completion: nil)
        extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
    }

    private func redirectToHostApp(type: RedirectType) {
        let url = URL(string: "ShareMedia://dataUrl=\(sharedKey)#\(type)")
        var responder = self as UIResponder?
        let selectorOpenURL = sel_registerName("openURL:")

        while (responder != nil) {
            if (responder?.responds(to: selectorOpenURL))! {
                let _ = responder?.perform(selectorOpenURL, with: url)
            }
            responder = responder!.next
        }
        extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
    }

    enum RedirectType {
        case media
        case text
        case file
    }

    func getExtension(from url: URL, type: SharedMediaType) -> String {
        let parts = url.lastPathComponent.components(separatedBy: ".")
        var ex: String? = nil
        if (parts.count > 1) {
            ex = parts.last
        }

        if (ex == nil) {
            switch type {
                case .image:
                    ex = "PNG"
                case .video:
                    ex = "MP4"
                case .file:
                    ex = "TXT"
            }
        }
        return ex ?? "Unknown"
    }

    func getFileName(from url: URL, type: SharedMediaType) -> String {
        var name = url.lastPathComponent

        if (name.isEmpty) {
            name = UUID().uuidString + "." + getExtension(from: url, type: type)
        }

        return name
    }

    func copyFile(at srcURL: URL, to dstURL: URL) -> Bool {
        do {
            if FileManager.default.fileExists(atPath: dstURL.path) {
                try FileManager.default.removeItem(at: dstURL)
            }
            try FileManager.default.copyItem(at: srcURL, to: dstURL)
        } catch (let error) {
            print("Cannot copy item at \(srcURL) to \(dstURL): \(error)")
            return false
        }
        return true
    }

    private func getSharedMediaFile(forVideo: URL) -> SharedMediaFile? {
        let asset = AVAsset(url: forVideo)
        let duration = (CMTimeGetSeconds(asset.duration) * 1000).rounded()
        let thumbnailPath = getThumbnailPath(for: forVideo)

        if FileManager.default.fileExists(atPath: thumbnailPath.path) {
            return SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video)
        }

        var saved = false
        let assetImgGenerate = AVAssetImageGenerator(asset: asset)
        assetImgGenerate.appliesPreferredTrackTransform = true
        //        let scale = UIScreen.main.scale
        assetImgGenerate.maximumSize =  CGSize(width: 360, height: 360)
        do {
            let img = try assetImgGenerate.copyCGImage(at: CMTimeMakeWithSeconds(600, preferredTimescale: Int32(1.0)), actualTime: nil)
            try UIImage.pngData(UIImage(cgImage: img))()?.write(to: thumbnailPath)
            saved = true
        } catch {
            saved = false
        }

        return saved ? SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video) : nil

    }

    private func getThumbnailPath(for url: URL) -> URL {
        let fileName = Data(url.lastPathComponent.utf8).base64EncodedString().replacingOccurrences(of: "==", with: "")
        let path = FileManager.default
            .containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
            .appendingPathComponent("\(fileName).jpg")
        return path
    }

    class SharedMediaFile: Codable {
        var path: String; // can be image, video or url path. It can also be text content
        var thumbnail: String?; // video thumbnail
        var duration: Double?; // video duration in milliseconds
        var type: SharedMediaType;

        init(path: String, thumbnail: String?, duration: Double?, type: SharedMediaType) {
            self.path = path
            self.thumbnail = thumbnail
            self.duration = duration
            self.type = type
        }

        // Debug method to print out SharedMediaFile details in the console
        func toString() {
            print("[SharedMediaFile] \n\tpath: \(self.path)\n\tthumbnail: \(self.thumbnail)\n\tduration: \(self.duration)\n\ttype: \(self.type)")
        }
    }

    enum SharedMediaType: Int, Codable {
        case image
        case video
        case file
    }

    func toData(data: [SharedMediaFile]) -> Data {
        let encodedData = try? JSONEncoder().encode(data)
        return encodedData!
    }
}

extension Array {
    subscript (safe index: UInt) -> Element? {
        return Int(index) < count ? self[Int(index)] : nil
    }
}
Dominik-domin commented 1 year ago

@didiabel thanks for the code, unfortunately it's not working for me :C I'm trying to debug this code and I've put a few prints, but when I open for example Youtube and try to share something, I got this error in Xcode: Share[4937:807452] [SRHDataCollectionController] Failed to find application record for ${BundleID}.ShareExtension becase (null)

I've checked every setting in XCode and I have the same like you guys, I don't know what's happening here :(

didiabel commented 1 year ago

@Dominik-domin if you want i can help you out someday when im free dm me

https://www.linkedin.com/in/joseph-abel-

csacchetti commented 1 year ago

I too like @Dominik-domin have tried all the suggestions you gave but the app still does not open just like the viedeo I posted above. @didiabel let me understand something. How do you have the target at 11.0 when xCode is giving you an error if you have a target less than 14.0. I use the latest versions of xCode and Flutter. Any person who can help me would be grateful. This problem has been stopping me for months. I have already lost a lot of time. Thanks

my doctor -v [✓] Flutter (Channel stable, 3.13.1, on macOS 13.5 22G74 darwin-arm64, locale it-IT) • Flutter version 3.13.1 on channel stable at /Users/carlosacchetti/development/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision e1e47221e8 (10 days ago), 2023-08-22 21:43:18 -0700 • Engine revision b20183e040 • Dart version 3.1.0 • DevTools version 2.25.0

[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.1) • Android SDK at /Users/carlosacchetti/Library/Android/sdk • Platform android-33, build-tools 33.0.1 • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301) • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 14.3.1) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 14E300c • CocoaPods version 1.12.1

[✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2022.1) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301)

[✓] VS Code (version 1.81.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.70.0

[✓] Connected device (3 available) • iPhone di CS (mobile) • 00008101-0015458A3488001E • ios • iOS 14.7.1 18G82 • macOS (desktop) • macos • darwin-arm64 • macOS 13.5 22G74 darwin-arm64 • Chrome (web) • chrome • web-javascript • Google Chrome 116.0.5845.140

[✓] Network resources • All expected network resources are available.

• No issues found!

bhagat-techind commented 1 year ago

I have fixed this issue in version 1.1.0

csacchetti commented 1 year ago

Thank you for the update. Now the app opens however the moment you try to share a file it creates this error and the app is killed. I don't know whether I should continue here or open a new issue.

This is the error in SwiftFlutterSharingIntentPlugin.swift at line 253:

Screenshot 2023-09-04 alle 17 06 55
[User Defaults] Couldn't read values in CFPrefsPlistSource<0x281d0f100> (Domain: group.com.myid.sharingIntentOk, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd
2
flutter_sharing_intent/SwiftFlutterSharingIntentPlugin.swift:253: Fatal error: The data couldn’t be read because it is missing.
* thread #1, queue = 'com.apple.main-thread', stop reason = Fatal error: The data couldn’t be read because it is missing.
    frame #0: 0x0000000194c16eac libswiftCore.dylib`_swift_runtime_on_report
libswiftCore.dylib`:
->  0x194c16eac <+0>: ret
libswiftCore.dylib`:
    0x194c16eb0 <+0>: b      0x194c16eac               ; _swift_runtime_on_report
libswiftCore.dylib`:
    0x194c16eb4 <+0>: adrp   x8, 337372
    0x194c16eb8 <+4>: ldrb   w0, [x8, #0x299]
Target 0: (Runner) stopped.
Lost connection to device.
Exited
simonegiammy commented 1 year ago

Stuck on the same error, the app only opens if I share a text, but it gives me the error above: [User Defaults] Couldn't read values in CFPrefsPlistSource<0x281d0f100> [...] I cannot understand this and also why it only opens with a text and not with photo, pdf etc.

afl-dev commented 1 year ago

Thank you for the update. Now the app opens however the moment you try to share a file it creates this error and the app is killed. I don't know whether I should continue here or open a new issue.

This is the error in SwiftFlutterSharingIntentPlugin.swift at line 253: Screenshot 2023-09-04 alle 17 06 55

[User Defaults] Couldn't read values in CFPrefsPlistSource<0x281d0f100> (Domain: group.com.myid.sharingIntentOk, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd
2
flutter_sharing_intent/SwiftFlutterSharingIntentPlugin.swift:253: Fatal error: The data couldn’t be read because it is missing.
* thread #1, queue = 'com.apple.main-thread', stop reason = Fatal error: The data couldn’t be read because it is missing.
    frame #0: 0x0000000194c16eac libswiftCore.dylib`_swift_runtime_on_report
libswiftCore.dylib`:
->  0x194c16eac <+0>: ret
libswiftCore.dylib`:
    0x194c16eb0 <+0>: b      0x194c16eac               ; _swift_runtime_on_report
libswiftCore.dylib`:
    0x194c16eb4 <+0>: adrp   x8, 337372
    0x194c16eb8 <+4>: ldrb   w0, [x8, #0x299]
Target 0: (Runner) stopped.
Lost connection to device.
Exited

any update?

ingramleedy commented 10 months ago

Now the app open, but it looks like it does not goes through the callback : FlutterSharingIntent.instance.getMediaStream().listen ...

I am having the same issue. Where you able to resolve this? Any tips or suggestion would be great! Thank you.

Dominik-domin commented 10 months ago

Now the app open, but it looks like it does not goes through the callback : FlutterSharingIntent.instance.getMediaStream().listen ...

I am having the same issue. Where you able to resolve this? Any tips or suggestion would be great! Thank you.

Unfortunately I couldn't fix it in any way so I had to find another package :/ I've used https://pub.dev/packages/share_handler/example and it worked for me