Skip to main content

Create Checkpointed Tutorials

info

Do you prefer learning through videos? Check out our video on Generate Code for Tutorial Apps

State in Bluehawk

Bluehawk has a concept we call state. This gives you the ability to represent different "states" for tutorials. For example, you might have a start state where a code example is a TODO: block. Then, you might have a final state where the actual code exists in that code block.

The Realm Docs team uses state to create checkpointed tutorials. The output of a given state becomes a git repository branch; i.e. the start branch. In this example, our Realm React Native tutorial's start branch features several TODO: blocks:

// TODO: Open the project realm with the given configuration and store
// it in the realmRef. Once opened, fetch the Task objects in the realm,
// sorted by name, and attach a listener to the Task collection. When the
// listener fires, use the setTasks() function to apply the updated Tasks
// list to the state.

The final branch has completed code, instead:

Realm.open(config).then((projectRealm) => {
realmRef.current = projectRealm;

const syncTasks = projectRealm.objects("Task");
let sortedTasks = syncTasks.sorted("name");
setTasks([...sortedTasks]);
sortedTasks.addListener(() => {
setTasks([...sortedTasks]);
});
});

We manage this in a single file using Bluehawk state. This simplifies testing and maintaining the tutorials.

Under the covers, state is an identifier that the Bluehawk CLI uses when it generates output.

You can use any string identifier you'd like as your state keyword; we use start, final, local, and sync based on what we are teaching in the tutorial. You use this identifier with a state or state-uncomment tag when you annotate a tutorial's code files.

Then, when you use the CLI to extract code snippets, you pass the state identifier you used when annotating the file to extract just the code snippets for that state. You pass this state as a flag to the Bluehawk CLI snip or copy commands.

Annotate the Tutorial

Start by annotating your tutorial with Bluehawk tags. As with code snippets, you open and close a code block with snippet-start and snippet-end. You can remove code if your tutorial contains tests or boilerplate you don't want to expose in your documentation. You can also replace awkward terms with more readable ones if you have any namespace issues, or want to rename things for consistency across docs.

The key to tutorials, though, is adding state annotations where you want the content to change based on which state you want to show. This example is from the Realm React Native Tutorial TasksProvider.js file.

We start a snippet called clean-up. Then you see // :state-start: final. In this code snippet, final is the identifier we use for the "final" branch in our clonable tutorial git repository. The code after the final state start is the code that developers see in that branch.

Below that, you see // :state-end: :state-uncomment-start: start all on the same line. This line ends the :state-start: final from a few lines above. Then, it starts a new state, called start, and uncomments the code in the state-uncomment section. The comment is "double commented" - after uncommenting, it shows as a regular comment.

// :snippet-start: clean-up
return () => {
// cleanup function
const projectRealm = realmRef.current;
if (projectRealm) {
// :state-start: final
projectRealm.close();
realmRef.current = null;
// :state-end: :state-uncomment-start: start
//// TODO: close the project realm and reset the realmRef's
//// current value to null.
// :state-uncomment-end:
setTasks([]);
}
};
// :snippet-end:

After we extract the snippets, this code block looks like this in the final state:

return () => {
// cleanup function
const projectRealm = realmRef.current;
if (projectRealm) {
projectRealm.close();
realmRef.current = null;
setTasks([]);
}
};

And this in the start state:

return () => {
// cleanup function
const projectRealm = realmRef.current;
if (projectRealm) {
// TODO: close the project realm and reset the realmRef's
// current value to null.
setTasks([]);
}
};

You can see Bluehawk has removed one of the levels of comment nesting. It shows only the TODO: block in the start state, while the final state shows only the final code - not the TODO block at all.

Use the CLI to Extract Snippets

After you have annotated your tutorial code, use the Bluehawk CLI to extract code snippets. The process is the same as when extracting code examples, because you're doing the same thing - you just pass an additional --state flag to indicate which state you want in the code example.

As with extracting code examples, you use the bluehawk snip command, and you can extract code from a single file or from a directory. But you must run the command more than once, and each time you pass it the state identifier whose code example you want to generate.

For example, for our annotated code above, we might use this command to generate code for the start state:

bluehawk snip -o source/tutorial/generated/code/start/ tutorial/rn/providers/TasksProvider.js --state=start

And then use this command to generate code for the final state:

bluehawk snip -o source/tutorial/generated/code/final/ tutorial/rn/providers/TasksProvider.js --state=final

The input file is the same in both cases. The output directory is different. Every output file follows the same naming convention, and there is nothing in the file name to indicate the state that was used to generate that file. In the example above, the generated file name would be TasksProvider.snippet.clean-up.js in both cases.

Include the Snippets In Your Documentation

After annotating your tutorial code and extracting code examples with the Bluehawk CLI, you've got code files in output directories.

Now it's time to include those code files in your documentation. How you do that depends on your documentation tooling. For info about how the Realm docs team includes code examples in our documentation, see: Include Code Snippets In Your documentation.

Automatically Update Tutorial Code

For our tutorial code, we do something extra. Our tutorial applications each live in clonable GitHub artifact repositories so developers can clone and run our tutorials. We have a GitHub Workflow that manages updating these tutorial repositories; we don't push to these repositories directly.

Instead, when we push to the tutorial directory in our documentation repository, this GitHub workflow does a few things:

The GitHub Action copies code examples for each Bluehawk state to a git branch whose name is the same as the state name. This means that our tutorial repositories are automatically updated when we make updates to the source code in our documentation repository.