Callbacks

Because Leanplum variables and resources are retrieved from the server asynchronously after start, you need to wait for their values to be downloaded before using them in your code. The proper way to do this is to use one of the callbacks provided by our SDK.

The SDK will use locally-cached values, if available, and changes will only be synced on start or forceContentUpdate. Some callbacks, however, only execute on start.

You can call these multiple times across different classes in your app. If you call one after the necessary variables (and/or files) for that callback have been synced, the code in the callback will just execute again immediately.

You can place these callbacks in different controllers and classes in your app. However, where you place the callback will influence when it is called (e.g. in viewDidLoad vs. viewWillAppear).

callbackexecuted when
on Start Responsestart finishes.
on Variables Changedstart or forceContentUpdate finish.
on Variables Changed And No Downloads Pendingstart or forceContentUpdate finish and all files have downloaded.
on Value Changedstart or forceContentUpdate finish and this variable has changed.
on File Readystart or forceContentUpdate finish and this file has been updated.

on Start Response

This callback is executed after the start call finishes and all variables are returned from the server (but file downloads may be pending).

You can use this callback to load a new view from the splash screen after start finishes, so you don't have to worry about checking when your variables have their values.

[Leanplum onStartResponse:^(BOOL success) {
  [window addSubview:menuView];
}];
[Leanplum start];
Leanplum.onStartResponse{ (success:Bool) in
  window.addSubview(menuView)
}
Leanplum.start()

Alternatively, you can add a responder using addStartResponseResponder, and the selector function will be executed as an onStartResponse callback.

[Leanplum addStartResponseResponder:self withSelector:@selector(addSubview:)];
...
- (void)addSubview:(BOOL) success {
  [window addSubview:menuView];
}
Leanplum.addStartResponseResponder(self, with: #selector(addSubview(success:)))
...
func addSubview(success:Bool){
  window.addSubview(menuView)
}

on Variables Changed

This callback is executed after the start and forceContentUpdate calls finish and all variables are returned from the server (but file downloads may be pending). It is also executed later if the user is in an experiment that can update in real time.

Here's an example of how to use this callback to set a button's text using a Leanplum variable:

DEFINE_VAR_STRING(startLabel, @"Start");
...
[Leanplum onVariablesChanged:^() {
  self.startButton.titleLabel.text = startLabel.stringValue;
}];
var startLabel = LPVar.define("startLabel", with: "Start")
...
Leanplum.onVariablesChanged { () in
  self.startButton.titleLabel.text = self.startLabel?.stringValue()
}

Alternatively, you can add a responder using addVariablesChangedResponder, and the selector function will be executed as an onVariablesChanged callback.

- (void)viewDidLoad {
  [super viewDidLoad];
  [Leanplum addVariablesChangedResponder:self withSelector:@selector(updateStartButton)];
}
...
- (void)updateStartButton {
  self.startButton.titleLabel.text = startLabel.stringValue;
}
override func viewDidLoad() {
  super.viewDidLoad()
  Leanplum.addVariablesChangedResponder(self, with: #selector(self.updateStartButton))
}
...
func updateStartButton() {
  self.startButton.titleLabel.text = self.startLabel?.stringValue()
}

on Variables Changed And No Downloads Pending

This callback is executed after start and forceContentUpdate finish and all variables and files are returned from the server (or no files had to be downloaded). Files work like variables, except that you might want to treat this event differently because downloading files takes more time than loading variables.

DEFINE_VAR_FILE(goldStar, @"gold_star.png");
...
- (void)loadMedia {
  [Leanplum onVariablesChangedAndNoDownloadsPending:^() {
    goldStarImage = [UIImage imageWithContentsOfFile:[goldStar fileValue]];
  }];
}
var goldStar = LPVar.define("goldStar", withFile: "gold_star.png")
func loadMedia(){
    Leanplum.onVariablesChangedAndNoDownloadsPending { () in
    let goldStarImage = UIImage.init(contentsOfFile: (goldStar?.fileValue())!)
    }
}

You can also add a responser using addVariablesChangedAndNoDownloadsPendingResponder, and the selector function will be executed as an onVariablesChangedAndNoDownloadsPending callback.

DEFINE_VAR_FILE(goldStar, @"gold_star.png");
[Leanplum addVariablesChangedAndNoDownloadsPendingResponder:self withSelector:@selector(loadMedia)];
...
- (void)loadMedia {
  goldStarImage = [UIImage imageWithContentsOfFile:[goldStar fileValue]];
}
var goldStar = LPVar.define("goldStar", withFile: "gold_star.png")
Leanplum.addVariablesChangedAndNoDownloadsPendingResponder(self, with: #selector(self.loadMedia))
...
func loadMedia() {
  let goldStarImage = UIImage.init(contentsOfFile: (goldStar?.fileValue())!)
}

on Value Changed

This callback is executed after the start and forceContentUpdate calls finish and an individual variable is updated. This lets you wait for a single variable, instead of all variables or files. Use the onValueChanged instance method on the LPVar object.

DEFINE_VAR_STRING(startLabel, @"Start");
...
[startLabel onValueChanged:^{
    self.startButton.titleLabel.text = startLabel.stringValue;
}];
var startLabel = LPVar.define("startLabel", with: "Start")
...
startLabel?.onValueChanged({
  self.startButton.titleLabel.text = self.startLabel?.stringValue()
})

on File Ready

This callback is executed after the start and forceContentUpdate calls finish and an individual file is downloaded. This lets you wait for a single file, instead of all of your files. Use the onFileReady instance method on the LPVar object.

DEFINE_VAR_FILE(goldStar, @"gold_star.png");
...
- (void)loadMedia {
  [goldStar onFileReady:^() {
     goldStarImage = [UIImage imageWithContentsOfFile:[goldStar fileValue]];
  }];
}
var goldstarImage = LPVar.define("goldStar", withFile: "gold_star.png")
func loadMedia(){
    goldStar?.onFileReady { () in
        goldStarImage = UIImage.init(contentsOfFile: (goldStar?.fileValue())!)
    }
}

Testing callbacks in debug mode

When you're testing variables in your app, it's often useful to tweak values and see the result in real time. If you use onVariablesChanged, your variables will update immediately after you change them in the Leanplum dashboard.

Real-time variable changes only happen when your app is run in debug mode (set in Xcode). Variables will not sync in real time in a production build. They will sync on start and forceContentUpdate.

If you want to mimic your users' experience and don't want to see real-time changes when testing, you can call onStartResponse instead, which is only triggered the first time variables receive their values on start.

Leanplum will tell you if it couldn't connect to the server, in which case the values will be whatever they were the last time the app was run (or their default values set in the code if the app hasn't been run yet).