While I mostly agree with DanV and jremington, I also slightly disagree - let me explain. What you are effectively asking is can a library use a callback function. When used in a library, a callback function gives the library user the option of modifying some part of the functionality provided by the library. But take note, there are two key words in the previous sentence - “option” and “some”. If you force the user to specify all the behavior of the library, then what’s the point of the library in the first place? I believe this is, to some extent, the point DanV and jremington are making (sorry, if I misread your posts).
In your case, I don’t think you’re forcing the user to specify all of the functionality provided by the library, but I think there are things you could do a little better. It appears to me your library does two things - store a fixed, pre-defined block of data onto an SD card and then store a user defined string after that. So the real functionality your library offers is storing this standard block of data - i.e. the user doesn’t have to “reinvent the wheel” each time they want to do this. The part I think you’re missing, is making the user-defined string, that’s stored after, optional. Right now, anyone that uses your library as is and forgets to define SampleDataString() will get compile errors. This will happen even if all they care about is storing the standard block of data. What I would recommend is having your library (weakly) define SampleDataString() to provide a default string (or no string), then if the user wants, they can override your definition of SampleDataString() with their own. If your library defines a class that the user instantiates, you can do this another way by having a constructor that takes a function pointer as an argument, stores it, and then your library uses this pointer to “call back” to the user code when the time is right.
As an example, look at the Arduino IDE’s use of the serialEvent() function. In the main program loop that the IDE creates, after it calls your loop() function, it then checks if serialEventRun is defined (which will be if you include HardwareSerial.h), and if so calls serialEventRun() which in turn calls serialEvent() whenever serial data is available. By default this function is weakly defined in HardwareSerial.cpp to do nothing, but the weak attribute, attribute((weak)), allows the user, in their sketch, to override this function, if they wish. So the Arduino IDE, a library of sorts (if you allow me to view it that way), provides the core functionality we all know and love - call setup() then repeatedly call loop() - as well as additional, and optional, functionality of alerting the user whenever serial data is available. To use this additional functionality, though, requires the user to define the serialEvent() function.
Anyway, I hope this helps or perhaps provides some alternate vantage point.
Mike