ioskotlinkotlin-multiplatformkotlin-multiplatform-mobilekmm

How to check whether a particular url path is a directory or not in KMM ios implementation?


I want to covert the corresponding swift method into kotlin for kmm

func checkIfDirectoryExist(fileURL: URL) -> Bool {
     var isDir: ObjCBool = false
     if FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDir)
     {
         if isDir.boolValue {
             return true
         } else {
             return false
         }
      }
        return false
}

However I am not sure hot to pass Boolean pointer in kotlin as there are no toCPointer<Boolean>() method neither ** works


Solution

  • Working with Objective-C APIs in Kotlin/Native can be complicated. In many cases such as this one, you're actually interacting with elements of C such as pointers and memory allocation.

    Understanding memory use for the isDirectory boolean in Swift

    Given the Swift code that you wrote:

    var isDir: ObjCBool = false
    if FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDir) {
       ...
    

    If we consider how this code is running it might help to understand how to write this in Kotlin/Native:

    Allocating memory for isDir in K/N using memScoped and alloc

    In Kotlin/Native, we need to allocate memory for the isDir variable to be populated, which relies on leveraging the kotlinx.cinterop suite of methods.

    Using memscoped will allocate memory for use within the block passed as an argument and deallocate that memory when the scope closes. You can see in the declaration of the function that whatever type we return from the block (R) will be the same value returned from the call to memScoped (R):

    inline fun <R> memScoped(block: MemScope.() -> R): R

    The MemScoped object allows calling the alloc method to allocate memory for use within this block, and we need to allocate memory for a BooleanVar. Note that alloc returns type T which conforms to CVariable.

    memScoped {
        val isDirectory = alloc<BooleanVar>()
        ...
    }
    

    Populating the value of isDirectory

    When calling the fileExists() method in K/N, if we check the K/N header declaration we can see what it expects to receive for arguments (formatted for readability). Note the isDirectory parameter is a CPointer pointing to a BooleanVar. This is how we knew to allocate memory for a BooleanVar and not some other kind of type:

    @kotlinx.cinterop.ObjCMethod 
    public open external fun fileExistsAtPath(
        path: kotlin.String, 
        isDirectory: kotlinx.cinterop.CPointer<kotlinx.cinterop.BooleanVar>?
    ): kotlin.Boolean
    

    Therefore, with the memory that we allocated, which is of type CVariable, we can pass the pointer to that memory (passing by reference) for the fileExistsAtPath method to change the value of it:

    val fileExists = NSFileManager.defaultManager.fileExistsAtPath(path, isDirectory.ptr)
                                                                                     ^^^
    

    Lastly, getting the value populated into the BooleanVar CVariable is accessed from the .value property.

    Together, the code might look something like:

    return memScoped {
        val isDirectory = alloc<BooleanVar>()
        val fileExists = NSFileManager.defaultManager.fileExistsAtPath(path, isDirectory.ptr)
        return@memScoped fileExists && isDirectory.value
    }
    

    iOS URL

    As for converting the entire method, this is getting a bit into API generalization. You'll need to convert the URL argument to a platform-independent type, such as a simple path (string). This appears to be unrelated to your original question, so although I'll leave this detail out, you might find this blog post helpful.

    If you want to go deeper

    Kotlin/Native has some documentation on its interoperability with C, however admittedly, I find this documentation a bit difficult to understand with the provided example.