Skip to content

Latest commit

 

History

History
305 lines (239 loc) · 10.6 KB

how_to_import_files.md

File metadata and controls

305 lines (239 loc) · 10.6 KB

How to open .ulpt files in place

On iOS

Enable custom file type for ulpt files on iOS:

  • Go to Project -> Targets -> Runner -> Info tab

  • Add Application supports iTunes file sharing with value true to the key value table or info.plist file

  • Add Supports opening documents in place with value true to the key value table or info.plist file

  • Add information about the extension to the Exported Type Identifiers Tab.

    • Description: Any description e.g. ULPT Performance Data
    • Identifier: Any identifier that is not reserved. E.g. org.frenchtacodev.ulpt
    • Conforms to: public.data, public.content (content is optional)
    • Extensions: ulpt
  • Add information about the extension to the Document Types Tab.

    • Name: Any name e.g. ULPT Performance Data
    • Types: Same as identifier e.g. org.frenchtacodev.ulpt
    • Handler Rank: Owner

Info.plist entry from ULPT Example:

<plist version="1.0">
<dict>
	<key>CFBundleDocumentTypes</key>
	<array>
		<dict>
			<key>CFBundleTypeName</key>
			<string>ULPT Performance Data</string>
			<key>LSHandlerRank</key>
			<string>Owner</string>
			<key>LSItemContentTypes</key>
			<array>
				<string>org.frenchtacodev.ulpt</string>
			</array>
		</dict>
	</array>
	<key>LSSupportsOpeningDocumentsInPlace</key>
	<true/>
	<key>UIFileSharingEnabled</key>
	<true/>
	<key>UTExportedTypeDeclarations</key>
	<array>
		<dict>
			<key>UTTypeConformsTo</key>
			<array>
				<string>public.data</string>
				<string>public.content</string>
			</array>
			<key>UTTypeDescription</key>
			<string>ULPT Performance Data</string>
			<key>UTTypeIconFiles</key>
			<array/>
			<key>UTTypeIdentifier</key>
			<string>org.frenchtacodev.ulpt</string>
			<key>UTTypeTagSpecification</key>
			<dict>
				<key>public.filename-extension</key>
				<array>
					<string>ulpt</string>
				</array>
				<key>public.mime-type</key>
				<array>
					<string>application/example</string>
				</array>
			</dict>
		</dict>
	</array>
</dict>
</plist>

Handle app call with arguments from native

Add the following code to the AppDelegate.swift file in the Flutter Project:

Define this variable within the file:

private var commChannel: FlutterMethodChannel? = nil;

Add this to the override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?

 	//GeneratedPluginRegistrant.register(with: self)
	let controller: FlutterViewController = window?.rootViewController as! FlutterViewController;
        commChannel = FlutterMethodChannel(name: "nativeCommChannel", binaryMessenger: controller.binaryMessenger);
        //return super.application(application, didFinishLaunchingWithOptions: launchOptions)

Override this funtion within the file:

override func application(_ app: UIApplication, open url: URL, options:[UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        
        if(commChannel == nil){return true;}
        
        var copyUrl: URL?;
        var copyUrlPath: String?;
                
        if #available(iOS 14.0, *) {
            copyUrl = FileManager.default.temporaryDirectory.appendingPathComponent(url.lastPathComponent, conformingTo: UTType.data);
        } else {
            copyUrl = FileManager.default.temporaryDirectory.appendingPathComponent(url.lastPathComponent);
        };
        
        if #available(iOS 16, *){
            copyUrlPath = copyUrl?.path();
        }else{
            copyUrlPath = copyUrl?.absoluteString;
        }
        
        if(copyUrl == nil || copyUrlPath == nil){return true;}
        
        do{
            if(FileManager.default.fileExists(atPath: copyUrlPath!)){
                try FileManager.default.removeItem(at: copyUrl!);
            }
            
            //If false, access is not granted
            if(url.startAccessingSecurityScopedResource() == false){return true;}
            try FileManager.default.copyItem(at: url, to: copyUrl!);
            url.stopAccessingSecurityScopedResource();
        }catch{
            url.stopAccessingSecurityScopedResource();
            return true;
        }
        
        commChannel!.invokeMethod("onArgsFromNative", arguments: copyUrlPath!);
        return true;
    }

File importing is then handled by ULPT.

On Windows

To support file opening on windows with the "Right click -> Open with" functionality, the following steps are needed:

  • Go into the projects windows folder. This is done on the app site and not in the package. E.g. in this project within the example folder/project.
  • The APIEntry wWinMain in the ´main.cpp´ file of the project contains a wchar_t pointer called command_line. We need this information

Implementation

  • open flutter_window.h and add under private:
private:
 // The project to run.
 flutter::DartProject project_;
 // The Flutter instance hosted by this window.
 std::unique_ptr<flutter::FlutterViewController> flutter_controller_;

 //Arguments passed on starting the win32 App
 std::string args_;
 //Utility function to convert the args into flutter encodable value.
 std::string ConvertArgsToString(const wchar_t* args);
  • change the constructor to:
explicit FlutterWindow(const flutter::DartProject& project, const wchar_t* args);
  • open flutter_window.cpp and add the following function implementation:
//Converts the arguments passed in from the command line that are represented as a wchar_t pointer. 
//Since flutter can only handle std::strings in method communication, conversion is needed.
std::string FlutterWindow::ConvertArgsToString(const wchar_t* args)
{
    std::string result = std::string();

    if (args == nullptr) return result;

    int length = WideCharToMultiByte(CP_UTF8, 0, args, -1, nullptr, 0, nullptr, nullptr);
    char* buffer = new char[length];
    WideCharToMultiByte(CP_UTF8, 0, args, -1, buffer, length, nullptr, nullptr);
    result = std::string(buffer);
    delete[] buffer;
    return result;   
}
  • modify the constructor implemenation this way:
FlutterWindow::FlutterWindow(const flutter::DartProject& project, const wchar_t* args) : project_(project){
    args_ = ConvertArgsToString(args);
}
  • in the OnCreate function add the following code after the ForceRedraw() call:
//Create and Invoke the Communication Channel to the flutter app letting it know about arguments if there are any 
if (args_ == "") return true;
flutter::MethodChannel channel(flutter_controller_->engine()->messenger(), "nativeCommChannel", &flutter::StandardMethodCodec::GetInstance());
channel.InvokeMethod(std::string("onArgsFromNative"), std::make_unique<flutter::EncodableValue>(flutter::EncodableValue(args_)));
  • in main.cpp function exchange FlutterWindow window(project); for FlutterWindow window(project, command_line); to call the constructor with the command line arguments.

Now when ULPT is opened from a file, the c++ side provides the path and the import should work.

On Android

Enable custom file type for ulpt files on Android:

Go into your apps AndroidManifest.xml and add the following lines under the MainActivities Activity Tab:

       <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data
                    android:scheme="file"
                    android:host="*"
                    android:pathPattern=".*\\.ulpt" />
            </intent-filter>
            <intent-filter android:priority="1">
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <category android:name="android.intent.category.OPENABLE" />

                <data android:host="*" />
                <data android:mimeType="application/octet-stream" />
                <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.ulpt" />
                <data android:pathPattern=".*\\..*\\..*\\..*\\.ulpt" />
                <data android:pathPattern=".*\\..*\\..*\\.ulpt" />
                <data android:pathPattern=".*\\..*\\.ulpt" />
                <data android:pathPattern=".*\\.ulpt" />
                <data android:scheme="content" />
          </intent-filter>

Handle app call with arguments from native

Add the following code to the MainActivity.java file in the Flutter Project (Android Side):

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        //Opens up flutters method channel to communicate with the dart side
        MethodChannel methodChannel = new MethodChannel(
                Objects.requireNonNull(this.getFlutterEngine()).getDartExecutor().getBinaryMessenger(),
                "nativeCommChannel"
        );


        Intent intent = getIntent();
        if(intent == null) return;
        String cachePath = handlePerfFileIntent(intent);
        if(cachePath == null) return;
        //Send the cached filepath to the dart side
        methodChannel.invokeMethod("onArgsFromNative", cachePath);
    }

    //This function takes the content intent and
    //1 reads its data, 2 copies the data to the app cache directory, 3 returns the cached files path.
    //This is necessary because we do not have permission to read storage files on android out of the box.
    private String handlePerfFileIntent(Intent intent){

        Uri uri = intent.getData();
        if(uri == null) return null;

        String path = getCacheDir() + "/" + "tempPerfData.ulpt";
        File outputFile = new File(path);
        if(outputFile.exists()) outputFile.delete();

        try {
            InputStream is = getContentResolver().openInputStream(uri);
            if(is == null) return null;
            FileOutputStream os = new FileOutputStream(outputFile, false);

            byte[] buffer = new byte[4096];
            int b;

            while((b = is.read(buffer)) != -1){
                os.write(buffer, 0, b);
            }

            os.close();
            is.close();
        } catch (IOException e) {
            return null;
        }

        return path;
    }