1

I need to read a text file with readLines() and I've already found this question, but the code in the answers always uses some variation of javaClass; it seems to work only inside a class, while I'm using just a simple Kotlin file with no declared classes. Writing it like this is correct syntax-wise but it looks really ugly and it always returns null, so it must be wrong:

val lines = object {}.javaClass.getResource("file.txt")?.toURI()?.toPath()?.readLines()

Of course I could just specify the raw path like this, but I wonder if there's a better way:

val lines = File("src/main/resources/file.txt").readLines()
rdxdkr
  • 839
  • 1
  • 12
  • 22
  • My guess is that you get null because of `toURI()?.toPath()` which wasn't present in the answers linked by you. `file.txt` does not exist anywhere in the filesystem and can't be accessed like a file. And I think `object {}` approach is just fine. Alternatively, you can use just any class from your module. – broot Dec 15 '21 at 10:25

4 Answers4

9

Thanks to this answer for providing the correct way to read the file. Currently, reading files from resources without using javaClass or similar constructs doesn't seem to be possible.

// use this if you're inside a class
val lines = this::class.java.getResourceAsStream("file.txt")?.bufferedReader()?.readLines()

// use this otherwise
val lines = object {}.javaClass.getResourceAsStream("file.txt")?.bufferedReader()?.readLines()

According to other similar questions I've found, the second way might also work within a lambda but I haven't tested it. Notice the need for the ?. operator and the lines?.let {} syntax needed from this point onward, because getResourceAsStream() returns null if no resource is found with the given name.

rdxdkr
  • 839
  • 1
  • 12
  • 22
  • 1
    Indeed, using `object {}` is a good workaround for when you're not inside a class. Btw if you're wondering whether you should use `javaclass` or `class.java`, see https://stackoverflow.com/questions/46674787/instanceclass-java-vs-instance-javaclass – k314159 Dec 15 '21 at 14:26
  • 2
    This worked for me after I did `/file.txt` i.e. `val lines = object {}.javaClass.getResourceAsStream("/file.txt")?.bufferedReader()?.readLines()` otherwise it was returning null – dftag Dec 25 '22 at 02:16
2

Kotlin doesn't have its own means of getting a resource, so you have to use Java's method Class.getResource. You should not assume that the resource is a file (i.e. don't use toPath) as it could well be an entry in a jar, and not a file on the file system. To read a resource, it is easier to get the resource as an InputStream and then read lines from it:

val lines = this::class.java.getResourceAsStream("file.txt").bufferedReader().readLines()
k314159
  • 5,051
  • 10
  • 32
  • Thank you, it worked. I'm adding my own answer with the version that uses `object {}.javaClass` which is needed in my case. – rdxdkr Dec 15 '21 at 12:37
1

I'm not sure if my response attempts to answer your exact question, but perhaps you could do something like this:

I'm guessing in the final use case, the file names would be dynamic - Not statically declared. In which case, if you have access to or know the path to the folder, you could do something like this:


// Create an extension function on the String class to retrieve a list of 
// files available within a folder. Though I have not added a check here
// to validate this, a condition can be added to assert if the extension
// called is executed on a folder or not
fun String.getFilesInFolder(): Array<out File>? = with(File(this)) { return listFiles() }

// Call the extension function on the String folder path wherever required
fun retrieveFiles(): Array<out File>? = [PATH TO FOLDER].getFilesInFolder()

Once you have a reference to the List<out File> object, you could do something like this:


// Create an extension function to read 
fun File.retrieveContent() = readLines()
// You can can further expand this use case to conditionally return
// readLines() or entire file data using a buffered reader or convert file
// content to a Data class through GSON/whatever.
// You can use Generic Constraints 
// Refer this article for possibilities
// https://kotlinlang.org/docs/generics.html#generic-constraints


// Then simply call this extension function after retrieving files in the folder.
listOfFiles?.forEach { singleFile -> println(singleFile.retrieveContent()) }
Clinkz
  • 716
  • 5
  • 18
  • I appreciate the answer but it's a bit overkill for what I need to do right now. Thank you anyway. – rdxdkr Dec 15 '21 at 13:00
1

In order to have the same url that work for both Jar or in local, the url (or path) needs to be a relative path from the repository root.

..meaning, the location of your file or folder from your src folder.

could be "/main/resources/your-folder/" or "/client/notes/somefile.md"

The url must be a relative path from the repository root.

it must be "src/main/resources/your-folder/" or "src/client/notes/somefile.md"

Now you get the drill, and luckily for Intellij Idea users, you can get the correct path with a right-click on the folder or file -> copy Path/Reference.. -> Path From Repository Root (this is it)

Last, paste it and do your thing.

Koch
  • 555
  • 4
  • 15