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]