Selecting location to save my app's preferences file

This is why you must never use a line like:

f = SpecialFolder.ApplicationData.Child("My App Name").child("Printer Settings")

You cannot test if the first Child (My App) is Nil, Exists, etc.
And your names are too common to be used; that is why people avice to use com.companyname as a folder name; after, you do what you want (and get crash now, or your customers get some…)

It works!!! :grinning:

OK, between the tips here and re-reading the documentation 100 times, the following code now successfully writes my printer settings to a prefs file located in the user’s Documents folder, in a subdirectory called LabelPrint (my app name).

Var pageSetup As PrinterSetup
pageSetup = New PrinterSetup
If pageSetup.ShowPageSetupDialog Then
  App.s = pageSetup.Settings
End If

// save printer settings to file

Var f As FolderItem
Var bs As BinaryStream

// create folder for my prefs inside the user's Documents folder
Var myData As FolderItem = SpecialFolder.Documents.Child("LabelPrint")

if myData.Exists Then 
  
  // get a folderitem
  f = SpecialFolder.Documents.Child("LabelPrint").child("Printer Settings")
  
  // create a binary file with the type of text (defined in the file types dialog)
  bs = BinaryStream.Create(f, True)
  
  // check to see if it was created
  If bs <> Nil Then
    //write the contents of the editField
    bs.Write(App.s.ConvertEncoding(Encodings.UTF8))
    // close the binaryStream
    bs.Close
  End If
  
Else 
  myData.CreateFolder
End If

I can now successfully read the prefs from this location at application launch. Whew!

2 Likes

That’s going to fail to save the settings the first time, when myData does not exist. Better to get myData check Not Exists and create the folder. Then your following code to save the settings will run every time.

// create folder for my prefs inside the user's Documents folder
Var myData As FolderItem = SpecialFolder.Documents.Child("LabelPrint")

if not myData.Exists then myData.CreateFolder

// get a folderitem
f = SpecialFolder.Documents.Child("LabelPrint").child("Printer Settings")
  
// create a binary file with the type of text (defined in the file types dialog)
bs = BinaryStream.Create(f, True)

etc.
3 Likes

Your logic is faulty. The first time it runs it will create the LablePrint folder, but it won’t do anything else. You should look at @Tim_Hare 's post or my post (upthread) of yesterday.

1 Like

Ugh. Yep. Will dig in more tomorrow! Thank you! This is super helpful on my learning path…

Looking at everything sequentially, one might list the steps as follows:

  1. The app starts and has NO IDEA what state the machine might be in. Well, I guess it knows that no one has unplugged the CPU :slight_smile: , at least.

  2. Here a miracle occurs

  3. Now the app knows everthing is just peachy and can go merrily on doing its thing.

What we’re after is step 2. Breaking that down into smaller steps is the thing.

  1. If we assume that the Documents folders is there [1], then the first step is whether LabelPrint exists. As @Tim_Hare 's post has it, check and create if not.

  2. Next step is whether the Printer Settings file exists or not. If not create it with the user’s settings (for example)

  3. You might also be creating a general Settings file with default settings if it doesn’t exist. If it does then you might feel you want to validate it. Or leave that as a future improvement, perhaps.

  4. and so on. At each of these steps you check and then might or might not take an action, depending.

[1] Is checking that the Documents folder exists akin to checking that the CPU is plugged in? Not sure.

Plus, especially on macOS, your app needs permission to write to the user’s Documents folder. Before your app is able to write, macOS will ask the user for permission. Which is probably confusing to the user. As written above, better to use Specialfolder.Applicationdata.child(“reverse URL of your app”)

2 Likes

Whew. What a great community! It turns out my code did break as mentioned by Tim and Tim – I inadvertently already had the directory created, so it worked for me… until I went back, removed it, tested and saw the failure. And some of the suggested code I already had… but in the wrong place.

Here’s my corrected code, which seems to work in all test conditions now. One block below is in my app’s Opening Event Handler, while the other code is under my Page Setup menu option.

Event Handler - Opening

// read printer preference settings from file
Var f As FolderItem
Var bs As BinaryStream

// create folder for my prefs inside the user's Documents folder
Var myData As FolderItem = SpecialFolder.Documents.Child("LabelPrint")

if not myData.Exists then myData.CreateFolder

// get a folderitem
f = SpecialFolder.Documents.Child("LabelPrint").child("Printer Settings")

// make sure it exists before we try to read it
If f.Exists Then
  
  // open the folderitem as a binary file without write privelages
  //     To open with write priviledges, use true instead of false
  bs = BinaryStream.Open(f, False)
  
  // make sure we have a binary stream to read from
  If bs <> Nil Then
    
    // read the whole binaryStream
    App.s = bs.Read(bs.Length, Encodings.UTF8)
    
    // close the binaryStream
    bs.Close
  End If
End If

and my creation of the Preferences file under my Page Setup menu handler:

Var pageSetup As PrinterSetup
pageSetup = New PrinterSetup
If pageSetup.ShowPageSetupDialog Then
  App.s = pageSetup.Settings
End If

// save printer settings to file

Var f As FolderItem
Var bs As BinaryStream


// get a folderitem
f = SpecialFolder.Documents.Child("LabelPrint").child("Printer Settings")

// create a binary file with the type of text (defined in the file types dialog)
bs = BinaryStream.Create(f, True)

// check to see if it was created
If bs <> Nil Then
  //write the contents of the editField
  bs.Write(App.s.ConvertEncoding(Encodings.UTF8))
  // close the binaryStream
  bs.Close
End If
1 Like

If you store a globalproperty (in the App.Open Event where you create the main folder for storing your application data), you will not need to do that line (with two Childs… Error prone).
You can also create a second global Property to store the printer settings folder reference and skip this line completely, but do not forget to test if these properties are Nil and Exists before continuing when (if) you will be there (went this road).

1 Like

@Emile_Schwarz Thanks!

I see that you have a couple of options from here to answer your query. I just wanted to add that I picked up a module called ModernPreferences that works really well for me, mainly because it does most of the work for me; it saves and reads preferences in ~/Library/Application Support/[app name]. I’m sorry that I can’t credit the author but the code doesn’t have a name and I can’t remember where I got it from. However for what it’s worth I’ve attached the zip file that I originally downloaded. I hope you find it useful.
xojo-modern-preferences-master.zip (8.5 KB)

1 Like

Thanks @Ian_Piper
Looking forward to checking this out.

I’m amazed that nobody has pointed out that Preferences on Mac typically go in the Preferences Folder. This is accessed by SpecialFolder.Preferences.

Now on macOS you have to access it via the NSUserDefaults or CFPreferences APIs. This is because it is cached by the system. I’m pretty sure MacOSLib has both as part of it. I have an NSUserDefaults module somewhere I could make available.

1 Like