Skip to content

Instantly share code, notes, and snippets.

@NitinPraksash9911
Last active May 6, 2024 15:31
Show Gist options
  • Save NitinPraksash9911/dea21ec4b8ae7df068f8f891187b6d1e to your computer and use it in GitHub Desktop.
Save NitinPraksash9911/dea21ec4b8ae7df068f8f891187b6d1e to your computer and use it in GitHub Desktop.
Unzipping file in android/kotlin
import java.io.*
import java.util.zip.ZipFile
/**
* UnzipUtils class extracts files and sub-directories of a standard zip file to
* a destination directory.
*
*/
object UnzipUtils {
/**
* @param zipFilePath
* @param destDirectory
* @throws IOException
*/
@Throws(IOException::class)
fun unzip(zipFilePath: File, destDirectory: String) {
File(destDirectory).run {
if (!exists()) {
mkdirs()
}
}
ZipFile(zipFilePath).use { zip ->
zip.entries().asSequence().forEach { entry ->
zip.getInputStream(entry).use { input ->
val filePath = destDirectory + File.separator + entry.name
if (!entry.isDirectory) {
// if the entry is a file, extracts it
extractFile(input, filePath)
} else {
// if the entry is a directory, make the directory
val dir = File(filePath)
dir.mkdir()
}
}
}
}
}
/**
* Extracts a zip entry (file entry)
* @param inputStream
* @param destFilePath
* @throws IOException
*/
@Throws(IOException::class)
private fun extractFile(inputStream: InputStream, destFilePath: String) {
val bos = BufferedOutputStream(FileOutputStream(destFilePath))
val bytesIn = ByteArray(BUFFER_SIZE)
var read: Int
while (inputStream.read(bytesIn).also { read = it } != -1) {
bos.write(bytesIn, 0, read)
}
bos.close()
}
/**
* Size of the buffer to read/write data
*/
private const val BUFFER_SIZE = 4096
}
/**
Copyright 2020 Nitin Prakash
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
@LikeJson
Copy link

I have a little idea.

        val destDir = File(destDirectory)
        if (!destDir.exists()) {
            destDir.mkdir()
        }

can be changed to

             File(destDirectory).run {
                if (!exists()) {
                    mkdirs()
                }
            }

@NitinPraksash9911
Copy link
Author

NitinPraksash9911 commented Aug 21, 2021

yes of course @LikeJson, I have updated this, thanks man

@marianpavel
Copy link

Can we think of a way to get a progress percentage from this?

@NitinPraksash9911
Copy link
Author

hmm... let me try on this buddy.

@nabeel-terafort
Copy link

This code is working ok but in Techno devices unzip folder create double directory i dont know how to fix this isssue.

@vivekpanchal
Copy link

can you share the working example of this ?

@NitinPraksash9911
Copy link
Author

@vivekpanchal give some time I will update with working example

@wu928320442
Copy link

thanks.
i find a bug.
add try catch if file like .DS_Store have a file not find exception and exit.

if (!entry.isDirectory) {
// if the entry is a file, extracts it
try {
extractFile(input, filePath)
} catch (e: Exception) {
e.printStackTrace()
}
} else {
// if the entry is a directory, make the directory
val dir = File(filePath)
dir.mkdir()
}

@NitinPraksash9911
Copy link
Author

thanks for point out the issue, you can create PR for this

@AmibeSkyfy16
Copy link

I had some trouble with the current example, I modified it to work for me. You can find it here https://github.com/AmibeSkyfy16/MariaDBServerFabricMC/blob/2.0/src/main/kotlin/ch/skyfy/mariadbserverfabricmc/utils/UnzipUtils.kt

@JmRedGPS
Copy link

i made progress work!

Im using this on coroutines and works smoothly, this is my approach.

`import java.io.*
import java.util.zip.ZipFile

/**

  • UnzipUtils class extracts files and sub-directories of a standard zip file to
  • a destination directory.

/
object UnzipUtils {
/
*
* @param zipFilePath
* @param destDirectory
* @param updateProgress interface to update progress on UI
* @throws IOException
*/
@throws(IOException::class)
fun unzip(
zipFilePath: File,
destDirectory: String,
updateProgress: (progress: Int) -> Unit
) {
File(destDirectory).run {
if (!exists()) {
mkdirs()
}
}
ZipFile(zipFilePath).use { zip ->
var entryCount = 0
val totalEntries = zip.entries().toList().size // files to unzip
zip.entries().asSequence().forEach { entry ->
zip.getInputStream(entry).use { input ->
val filePath = destDirectory + File.separator + entry.name
if (!entry.isDirectory) {
// if the entry is a file, extracts it
extractFile(input, filePath)
} else {
// if the entry is a directory, make the directory
val dir = File(filePath)
dir.mkdir()
}
entryCount++
updateProgress(((entryCount.toDouble() / totalEntries) * 100).toInt())
}
}
}
}

/**
 * Extracts a zip entry (file entry)
 * @param inputStream
 * @param destFilePath
 * @throws IOException
 */
@Throws(IOException::class)
private fun extractFile(inputStream: InputStream, destFilePath: String) {
    //log("Descomprimiendo archivo: $destFilePath")
    val bos = BufferedOutputStream(FileOutputStream(destFilePath))
    val bytesIn = ByteArray(BUFFER_SIZE)
    var read: Int
    while (inputStream.read(bytesIn).also { read = it } != -1) {
        bos.write(bytesIn, 0, read)
    }
    bos.close()
}

/**
 * Size of the buffer to read/write data
 */
private const val BUFFER_SIZE = 4096

}`

Using Unit lambda as a parameter you can directly interface both process and UI

UnzipUtils.unzip(file, fileFolder) { progress -> print("$progress") }

@oluwabajio
Copy link

oluwabajio commented Jan 3, 2023

This does not work for zip files with sub folders. SO i modified the code.

                if (!entry.isDirectory) {
                    // if the entry is a file, extracts it
                    val parentFolder = File(getParentDirPath(filePath))

                    if (!parentFolder.exists()) {
                        parentFolder.mkdirs()
                    }
                    extractFile(input, filePath)

                } else {
                    // if the entry is a directory, make the directory
                    val dir = File(filePath)
                    dir.mkdir()
                }

Method

  fun getParentDirPath(fileOrDirPath: String): String? {
        val endsWithSlash = fileOrDirPath.endsWith(File.separator)
        return fileOrDirPath.substring(
            0, fileOrDirPath.lastIndexOf(
                File.separatorChar,
                if (endsWithSlash) fileOrDirPath.length - 2 else fileOrDirPath.length - 1
            )
        )
    }

@mujahid-dzinemedia
Copy link

@FoamyGuy
Copy link

@NitinPraksash9911 would you consider specifying a license for this code? I know you published it publicly here and an the medium blog post but without a license included anywhere it's difficult to know what kind of re-use is permissible and under what conditions.

@NitinPraksash9911
Copy link
Author

updated @FoamyGuy

@FoamyGuy
Copy link

🎉 Thank you!

@Wb11G
Copy link

Wb11G commented Dec 19, 2023

val folder = applicationContext.getExternalFilesDir(test.zip)
val destinationPath = "/storage/emulated/0/advertisments/unzip"
SystemUtils().unzip(folder!!, destinationPath)

why always get error Caused by: java.util.zip.ZipException: error in opening zip file?

@elect86
Copy link

elect86 commented Apr 17, 2024

Ps: you may want to change it as

    @Throws(IOException::class)
    infix fun File.unzip(destDirectory: String) {
        File(destDirectory).run {
            if (!exists())
                mkdirs()
        }
        ZipFile(this).use { zip ->
            zip.entries().asSequence().forEach { entry ->
                zip.getInputStream(entry).use { input ->
                    val filePath = destDirectory / entry.name
                    if (entry.isDirectory) // if the entry is a directory, make the directory
                        File(filePath).mkdir()
                    else // if the entry is a file, extracts it
                        input.copyTo(FileOutputStream(filePath))
                }
            }
        }
    }

@Bahir
Copy link

Bahir commented May 6, 2024

                val filePath = destDirectory / entry.name

To avoid ambiguity of String and URI
val filePath: String = "${destDirectory}/${entry.name}"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment