title: Getting file count in a ZIP archive using SAF on Android
body: <p>I'm currently making a comic viewer app, where user can select a folder that contains zip files using SAF (<code>Intent.ACTION_OPEN_DOCUMENT_TREE</code>). Doing so returns <code>DocumentFile</code>, from which I can use <code>DocumentFile.fromTreeUri()</code> to eventually get <code>List&lt;Uri&gt;</code> of zip files inside that directory. Then, when user selects that file, an <code>Uri</code> is sent to <code>fun unzipFile()</code> inside of my model, that asyncronically:</p>
  <ol>
  <li>Opens input stream to that file <code>val inputStream = this.contentResolver.openInputStream(zippedFileUri)</code></li>
  <li>Uses <code>ZipInputStream</code> (either java or zip4j one) to read its contents <code>ZipInputStream(inputStream).use {/*do something with the file*/}</code></li>
  <li>Iterates over all entries, while also checking if it's not a directory using <code>ZipEntry</code> or <code>LocalFileHeader</code>:</li>
  </ol>
  <pre><code>while (zipInputStream.nextEntry.also { localFileHeader = it } != null) {
        if (localFileHeader?.isDirectory == true) continue
        val bytes = zipInputStream.readBytes()
        /* read bitmap from bytes and insert it to a LiveData variable*/
  }
  </code></pre>
  <p>This, though not the fastest and prettiest solution, works mostly fine as I then pass that data to a RecyclerView Adapter.</p>
  <p>The problem, however, starts when I try getting file count for every zip file in that directory. I don't know any better way to do so than to just reuse my <code>unzipFile()</code> code, but instead of reading bytes - incrementing some variable <code>size+=1</code>. This definitely works, but getting the data from just a few zip files with 70 or so files inside of them takes at best 2 minutes. Even though I insert that data to the database, the problem of reading it the first time still remains.</p>
  <p>Below is my code snippet of said function that takes ages. I run it in a model, that gets a list of objects that contain said <code>zippedFileUri</code> (here as <code>it.uri</code>) and tries to get them all at the same time:</p>
  <pre><code>viewModelScope.launch {
      try {
          mangas.map {
              async(Dispatchers.IO) {
                  val pageCount = getZipSize(context.contentResolver, it.uri)
              }
          }.awaitAll()
      } catch (e: Exception) {
          e.printStackTrace()
      }
  }
  
  private fun getZipSize(contentResolver: ContentResolver, uri: Uri): Int {
      var size: Int = 0
      try {
          val inputStream = contentResolver.openInputStream(uri)
          ZipInputStream(inputStream).use { zipInputStream -&gt;
              while (true) {
                  zipInputStream.nextEntry ?: break
                  size += 1
              }
          }
      } catch (e: Exception) {
          e.printStackTrace()
      }
      return size
  }
  </code></pre>
  <p>Is there any better way to get that information? From looking at Play Store apps I know it can be done, but not sure how. One of them claims it uses <code>minizip-ng</code> for zip files, but I'm clueless when it comes to adding C code to Android.</p>
  <p>Using things like <code>ZipFile</code>, be it from zip4j or java won't cut it as the returned <code>Uri</code> is not an actual file, but instead has something to do with <code>ContentResolver</code>, not really sure how that works. While at it, maybe there is also a better way to unzip files in memory?</p>
  <p>I tried some better or worse things but to no avail. For example:</p>
  <pre><code>val p = FileSystems.getDefault().getPath(zippedFileUri.path)
  val f = p.toFile()
  val zF = ZipFile(f)
  Log.i(&quot;V&quot;, &quot;${zF.isValidZipFile}&quot;) //this returns false
  </code></pre>