You are currently viewing Using coroutines to save my tree-like modal to the file system. Is my (beginner) approach okay?

Using coroutines to save my tree-like modal to the file system. Is my (beginner) approach okay?

In my JavaFX program I am attempting to save my modal to the users file system without freezing the UI. I’m trying to do this with kotlin coroutines JavaFX dispatcher. This is pretty much my first attempt with kotlin coroutines so I’m concerned I’m making simple errors.

For simplicities sake the modal consists of:

  • Book class, which contains a list of instances of a…
  • Page class, which contains a list of instances of a…
  • Content class.

This naturally lends itself to a tree like structure, and I want this structure to be similar on the user file system (There is a book folder, inside of which is a folder for each page, and inside each page is a text file of whatever is in the content class).

Previously in other projects I simply created a FileIO class that dealt with saving and loading the modal from the file system. This often became bloated so I wanted to try something more organized this time.

Firstly I created a Savable interface which consists of a single function suspend fun save(saveFolder: File). Book, Page and Content each implement this interface. The idea is each class is responsible for saving itself to the file system.

Secondly I had the Book class extend CoroutineScope. My naive implementation of its save function was:

override suspend fun save(saveFolder: File) { val jobs: List<Job> = pages.map { page -> coroutineScope { launch { page.save(saveFolder) } } } jobs.joinAll() // wait until all jobs are finished } 

After some research I saw that Here It says “A parent coroutine always waits for completion of all its children. A parent does not have to explicitly track all the children it launches, and it does not have to use Job.join to wait for them at the end”. I also read from this reddit comment that there should be a single top level scope that all other coroutines are launched in. Since it appears that coroutineScope {} creates a new scope I think I should instead be passing the Book scope in the Savable save function, which now looks like: suspend fun save(saveFolder: File, scope: CoroutineScope)

The save function of Book is now:

fun saveBook() { launch { val folderToSaveIn = askUserForFolder() save(folderToSaveIn, this) } } override suspend fun save(saveFolder: File, scope: CoroutineScope) { pages.map { page -> scope.launch { page.save(saveFolder, scope) } } } 

And likewise the Page save:

override suspend fun save(saveFolder: File, scope: CoroutineScope) { // creating the page folder omitted contents.map { content -> scope.launch { content.save(pageFolder, scope) } } } 

And finally the Content save just creates a textfile with its contents.

  • Is this approach an okay way to go about the problem?
  • If one of the coroutines hits an IOException for whatever reason, is it okay to just call scope.cancel() to shut the whole thing down?
  • What pitfalls should I look out for, or what features should I implement here? I’m looking to eventually license this software out to users so I want to really get the saving/loading done right.

I am new to coroutines so nudges and pointers to improve my approach would be welcome!

submitted by /u/SamKeene
[link] [comments]