Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all 4396 articles
Browse latest View live

Video Tutorial: Beginning iOS Debugging Part 4: Inspecting Variables


Video Tutorial: Beginning iOS Debugging Part 5: Challenge: Breakpoints

Kotlin Android Extensions

$
0
0
Featured Image

Android Kotlin Extensions

The Kotlin programming language has improved the Android development experience in many ways, especially since the announcement in 2017 of first-party support for the language. Null safety, extensions, lambdas, the powerful Kotlin standard library and many other features all add up to a better and more enjoyable way to make Android apps.

In addition to the Kotlin language, JetBrains also has developed a plugin for Android called the Kotlin Android Extensions, which was created to further ease everyday Android development. The extensions are a Kotlin plugin that every Android developer should be aware of.

In this tutorial, you’ll explore many of the features that come with the extensions, including the following main features:

View binding

View binding enables you to refer to views just like any other variable, by using synthethic properties, without the need to initialize view references by calling the ubiquitous findViewById().

Note: If you have used Butter Knife in the past, you’ll know that it also helps with view binding. However, the binding is done in a different way. Butter Knife requires you to write a variable and annotate it with the identifier of the corresponding view. Also, inside methods like Activity onCreate(), you need to call a method that will bind all those annotated variables at once. Using the Kotlin Android Extensions, you don’t need to annotate any variable. The binding is done the first time you need to access the corresponding view and saved into a cache. More on this later.

LayoutContainer

The LayoutContainer interface allows you to use view binding with views such as the ViewHolder for a RecyclerView.

Parcelize annotation

The @Parcelize annotation saves you from having to write all the boilerplate code related to implementing the Parcelable interface.

Note: This tutorial assumes you have previous experience with developing for Android in Kotlin. If you are unfamiliar with the language, have a look at this tutorial. If you’re beginning with Android, check out some of our Getting Started and other Android tutorials.

Getting started

The project you’ll be working with, YANA (Yet Another Notepad App), is an app to write notes. Use the Download Materials button at the top or bottom of this tutorial to download the starter project.

Once downloaded, open the starter project in Android Studio 3.0.1 or greater, and give it a build and run. Tap the floating action button to add a note, and tap the back button to save a note into your list.

app no notes app note app single note
app urgent note app multiple notes

Taking a look at the project, you see that it consists of two activities:

  • NoteListActivity.kt: The main activity that lists all your existing notes and lets you create or edit one.
  • NoteDetailActivity.kt: This will show an existing note and also can handle a new note.

Setting up Kotlin Android Extensions

Open the build.gradle file for the app module and add the following, just below the ‘kotlin-android’ plugin:

apply plugin: 'kotlin-android-extensions'

That’s all the setup you need to use the plugin! Now you’re ready to start using the extensions.

Note: If you create a new Android app with Kotlin support from scratch, you’ll see that the plugin is already included in the app build.gradle file.

View binding

Open NoteListActivity, and remove the following lines from the onCreate method:

val noteListView: RecyclerView = findViewById(R.id.noteListView)
val addNoteView: View = findViewById(R.id.addNoteView)

Android Studio should notice that it’s missing imports for your two views. You can hit Option-Return on macOS or Alt-Enter on PC to pull in the imports. Make sure to choose the import that starts with kotlinx to use the extensions.

If you check the imports now at the top of the file, you should see the following:

import kotlinx.android.synthetic.main.activity_note_list.*

In order to generate the synthetic properties, to reference the views of the layout, you need to import kotlinx.android.synthetic.main.<layout>.*. In this case, the layout is activity_note_list. The asterisk wild-card at the end means that all possible views will be pulled in from the file. You can also import views individually if you wish, by replacing the asterisk with the view name.

Your `onCreate()` method should now look like this:


override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_note_list)
    
    noteRepository = ...
    
    adapter = ...
    
    // 1
    noteListView.adapter = adapter
    // 2
    addNoteView.setOnClickListener {
      addNote()
    }
  }
  1. To reference the list that shows notes, check the `id` in the activity_note_list.xml file, you’ll find that it’s noteListView. So, you access it using noteListView.
  2. The floating action button has the `id` addNoteView, so you reference it using addNoteView.

It’s important to note here that your synthetic view reference has the same name as the `id` you used in the layout file. Many teams have their own naming conventions on XML identifiers, so you may need to update your convention if you want to stick to camel case on the view references in your code.

Next, open NoteDetailActivity and remove the following view properties:

private lateinit var editNoteView: EditText
private lateinit var lowPriorityView: View
private lateinit var normalPriorityView: View
private lateinit var highPriorityView: View
private lateinit var urgentPriorityView: View
private lateinit var noteCardView: CardView

Also, remove the findViewById calls in onCreate().

The project will not compile at this point because all of those undeclared views. However, if you take a look into the view ids of activity_note_detail.xml and note_priorities_chooser_view.xml, you’ll notice they match the undeclared views you have in the activity. Add the following imports to the top of the file:

import kotlinx.android.synthetic.main.activity_note_detail.*
import kotlinx.android.synthetic.main.note_priorities_chooser_view.*

Now the project should compile again :]

Finally, do similar with NoteListAdapter, and remove the following in NoteViewHolder:

private val noteTextView: TextView = itemView.findViewById(R.id.noteTextView)
private val noteDateView: TextView = itemView.findViewById(R.id.noteDateView)
private val noteCardView: CardView = itemView.findViewById(R.id.noteCardView)

Add the following import to the file:

import kotlinx.android.synthetic.main.note_item.view.*

Note: in a view you have to import kotlinx.android.synthetic.main.<layout>.view.*

And then prepend each referenced view with itemView, like so:

fun bind(note: Note, listener: Listener) {
  itemView.noteTextView.text = note.text
  itemView.noteCardView.setCardBackgroundColor(
    ContextCompat.getColor(itemView.noteCardView.context, note.getPriorityColor()))
  itemView.noteCardView.setOnClickListener {
    listener.onNoteClick(itemView.noteCardView, note)
  }

  itemView.noteDateView.text = sdf.format(Date(note.lastModifed))
}

Build and run the app, and you’ll see that everything is working like before, and you’ve removed all the findViewById() boilerplate. :]

View binding under the hood

I bet you’re curious about how this “magic” works.

Curious about the magic

Fortunately, there is a tool to decompile the code!

Open NoteListActivity and go to Tools > Kotlin > Show Kotlin Bytecode and then press the Decompile button.

Decompile

You’ll find the following method, generated by the plugin:

public View _$_findCachedViewById(int var1) {
  if(this._$_findViewCache == null) {

    this._$_findViewCache = new HashMap();
  }

  View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
  if(var2 == null) {
    var2 = this.findViewById(var1);
    this._$_findViewCache.put(Integer.valueOf(var1), var2);
  }

  return var2;
}

Now check that this method is called whenever you reference a view by right-clicking on it and selecting Find Usages. One example usage is:

RecyclerView var10000 = (RecyclerView)this._$_findCachedViewById(id.noteListView);

_$_findCachedViewById() creates a view cache HashMap, tries to find the cached view, and, if it doesn’t find it, then calls good old findViewById() and saves it to the cache map.

Pretty cool right? :]

Note: You’ll see that a _$_clearFindViewByIdCache was also generated, but the Activity doesn’t call it. This method is only needed when using Fragments, as the Fragment’s onDestroyView() calls it.

Check what the plugin does with the adapter. Open NoteListAdapter and decompile it.

To your surprise, you won’t find the _$_findCachedViewById method. Instead, you’ll find that each time that the bind() method is called, findViewById() is called. This leads to a performance problem (because it will always have to find the views through the hierarchy), the exact problem that a ViewHolder should solve. So, this is not following the ViewHolder pattern!

To avoid this, you could workaround with the following approach:

class NoteViewHolder(itemView: View)
  : RecyclerView.ViewHolder(itemView) {

  private val noteTextView = itemView.noteTextView
  private val noteCardView = itemView.noteCardView
  private val noteDateView = itemView.noteDateView

  private val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault())

  fun bind(note: Note, listener: Listener) {
    noteTextView.text = note.text
    noteCardView.setCardBackgroundColor(
        ContextCompat.getColor(noteCardView.context, note.getPriorityColor()))
    noteCardView.setOnClickListener {
      listener.onNoteClick(noteCardView, note)
    }
    
    noteDateView.text = sdf.format(Date(note.lastModifed))
  }
}

Now, if you decompile, you’ll see that findViewById() is only called when the NoteViewHolder is created, so you’re safe again!

However, there is another approach. You can use the LayoutContainer interface, which will be covered in the following section.

Experimental features

Certain features of the Kotlin Android Extensions have not yet been deemed production ready, and are considered experimental features. These include the LayoutContainer interface and the @Parcelize annotation.

To enable the experimental features, open the app module build.gradle file again and add the following, just below the ‘kotlin-android-extensions’ plugin:

androidExtensions {
  experimental = true
}

LayoutContainer

As you’ve seen, it’s easy to access views with synthethic properties by using the corresponding kotlinx imports. This applies to both activities and fragments.

But, in the case of a ViewHolder (or any class that has a container view), you can implement the LayoutContainer interface to avoid workarounds like the one you used before.

Open again NoteListAdapter and implement the LayoutContainer interface.

// 1
import kotlinx.android.extensions.LayoutContainer
// 2
import kotlinx.android.synthetic.main.note_item.*

...

// 3
class NoteViewHolder(override val containerView: View)
  : RecyclerView.ViewHolder(containerView), LayoutContainer {

  private val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault())

  fun bind(note: Note, listener: Listener) {
    // 4
    noteTextView.text = note.text
    noteCardView.setCardBackgroundColor(
        ContextCompat.getColor(noteCardView.context, note.getPriorityColor()))
    noteCardView.setOnClickListener {
      listener.onNoteClick(noteCardView, note)
    }
    
    noteDateView.text = sdf.format(Date(note.lastModifed))
  }
}
  • Import the LayoutContainer interface.
  • To reference the views of the note_item.xml layout using LayoutContainer you need to import kotlinx.android.synthetic.main.note_item.*
  • Add the interface to NoteViewHolder. To comply with it, you provide a containerView property override in the primary constructor, which then gets passed along to the superclass.
  • Finally, use the properties that reference the views of the layout.

If you decompile this code, you’ll see that it uses _$_findCachedViewById() to access the views.

Build and run the app to see the app working just like before, this time with LayoutContainer.

View caching strategy

You’ve seen that _$_findCachedViewById uses a HashMap by default. The map uses an integer for the key and a view object for the value. You could use a SparseArray for the storage instead.

If you prefer using a SparseArray, you can annotate the Activity/Fragment/ViewHolder with:

@ContainerOptions(cache = CacheImplementation.SPARSE_ARRAY)

If you want to disable the cache, the annotation is:

@ContainerOptions(cache = CacheImplementation.NO_CACHE)

It’s also possible to set a module-level caching strategy by setting the defaultCacheImplementation value in the androidExtensions in the build.gradle file.

@Parcelize

Implementing the Parcelable interface on a custom class allows you to add instances of the class to a parcel, for example, adding them into a Bundle to pass between Android components. There is a fair amount of boilerplate needed to implement Parcelable. Libraries like AutoValue have been created to help with that boilerplate.

The Kotlin Android Extensions have their own way to help you implement Parcelable, using the @Parcelize annotation.

Open the Note class and modify it to the following:

@Parcelize
data class Note(var text: String,

                var priority: Int = 0,
                var lastModifed: Long = Date().time,
                val id: String = UUID.randomUUID().toString()) :
    Parcelable

You’ve removed literally all the code in the body of the class, and replaced it with the single annotation.

Note: Android Studio may highlight the class thinking there’s a compile error. But this is a known bug, and here is the issue. Fear not, you can build and run the project without any problems.

Build and run the app, and all is working as before. Implementing Parcelable is just that simple! Imagine how much time this will save you. :]

Basic Happy

Where To Go From Here?

Congratulations! You’ve just learned the Kotlin Android Extensions, and seen how they let you remove a ton of boilerplate code from your project.

You can download the final version of the project using the Download Materials button at the top or bottom of this tutorial.

Here are some great references to learn more about the development of Kotlin Android Extensions:

Finally, as a separate project from Kotlin Android Extensions, Google has released Android KTX. KTX is not a plugin, but instead another set of extensions to ease Android development. KTX simplifies working with strings, SharedPreferences, and other parts of Android.

Feel free to share your feedback, findings or ask any questions in the comments below or in the forums. I hope you enjoyed this tutorial on the Kotlin Android Extenstions!

The post Kotlin Android Extensions appeared first on Ray Wenderlich.

Video Tutorial: Beginning iOS Debugging Part 6: Control Flow

Video Tutorial: Beginning iOS Debugging Part 7: Call Stack

Procedural Generation Of Mazes With Unity

$
0
0

Note: This tutorial was written using Unity 2017.1.0 and is aimed at advanced users. It assumes you are already comfortable programming games in Unity. Feel free to start with our basic Unity tutorials before doing this one.

Procedurally Generated Maze in Unity

As a Unity developer, you’re probably quite skilled at constructing levels manually. But have you ever wanted to generate levels on-the-fly? Procedural generation of meshes for floors and walls, as opposed to simply laying out preconstructed models, offers a lot of flexibility and interesting replay value.

In this tutorial, you’ll learn how to:

  • Procedurally generate levels by making a maze-running game.
  • Generate maze data.
  • Use maze data to build a mesh.

Getting Started

Most algorithms you can find (such as the ones here and here) produce “perfect”, dense mazes; that is, ones with only one correct path and no loops. They’re much like the ones you would find in a newspaper’s puzzle section.

Perfect maze

Image courtesy of Wikipedia

However, most games play better with mazes that are both imperfect, with looping paths, and sparse, made from open spaces instead of tight twisty corridors. This is especially true of the rogue-like genre, where procedural levels aren’t so much “mazes” as they are dungeons.

Roguelike dungeon

Image courtesy of Bob Nystrom

In this tutorial, you’re going to implement one of the simplest maze algorithms around, described here. The reason for this choice is simply to get mazes into your game with the least amount of effort. This simple approach works well for the classic games at the above link, so you’ll use that same algorithm to create the mazes in a game called Speedy Treasure Thief.

In this game, every level is a new maze that includes a treasure chest somewhere in the level. However, you don’t have much time to find it and get out before the guards come back! Each level has a time limit, and you can keep playing until you get caught. Your score is based on how much treasure you snag.

Screenshot of finished game

To start off, create a new empty project in Unity.

Now download the starter package, unzip it and import **proc-mazes-starter.unitypackage** into your new project. The starter package includes the following:

  1. A Graphics folder, which contains all the graphics required for the game.
  2. The Scene scene, which is the initial scene for this tutorial and contains the player and the UI.
  3. A folder called Scripts, which contains two helper scripts. You’ll write the rest during this tutorial.

That’s enough to get you started. You’ll go into each of these areas in detail later.

Establishing the Code Architecture

Start by adding an empty object to the scene. Select GameObject ▸ Create Empty, name it Controller and position it at (X:0, Y:0, Z:0). This object is simply an attachment point for the scripts that control the game.

In the project’s Scripts directory, create a C# script named GameController, then create another script and name it MazeConstructor. The first script will manage the overall game, while the second will specifically handle the maze generation.

Replace everything in GameController with the following:

using System;
using UnityEngine;

[RequireComponent(typeof(MazeConstructor))]               // 1

public class GameController : MonoBehaviour
{
    private MazeConstructor generator;

    void Start()
    {
        generator = GetComponent<MazeConstructor>();      // 2
    }
}

Here’s a quick summary of what you have just created:

  1. The RequireComponent attribute ensures that a MazeConstructor component will also be added when you add this script to a GameObject.
  2. A private variable that stores a reference returned by the GetComponent().

Add this script into the scene: drag the GameController script from the Project window and drop it on Controller GameObject in the Hierarchy window.

Notice that MazeConstructor was also added to Controller; that happened automatically because of the RequireComponent attribute.

Now in MazeConstructor, replace everything with the following:

using UnityEngine;

public class MazeConstructor : MonoBehaviour
{
    //1
    public bool showDebug;
    
    [SerializeField] private Material mazeMat1;
    [SerializeField] private Material mazeMat2;
    [SerializeField] private Material startMat;
    [SerializeField] private Material treasureMat;

    //2
    public int[,] data
    {
        get; private set;
    }

    //3
    void Awake()
    {
        // default to walls surrounding a single empty cell
        data = new int[,]
        {
            {1, 1, 1},
            {1, 0, 1},
            {1, 1, 1}
        };
    }
    
    public void GenerateNewMaze(int sizeRows, int sizeCols)
    {
        // stub to fill in
    }
}

Here’s what’s going on above:

  1. These fields are all available to you in the Inspector. showDebug will toggle debug displays, while the various Material references are materials for generated models. Incidentally, the SerializeField attribute displays a field in the Inspector, even though the variable is private as far as code access is concerned.
  2. Next is the data property. The access declarations (i.e. declaring the property as public but then assigning private set) makes it read-only outside this class. Thus, maze data can’t be modified from outside.
  3. As for the data type, the maze boils down to a grid of cells. Thus the maze data is simply a two-dimensional array that’s either 0 or 1 (to represent open or blocked) for every space. It’s that simple!

  4. The last bit of interesting code is in Awake(). This initializes data with a 3 by 3 array of ones surrounding zero. 1 means “wall” while 0 means “empty”, so this default grid is simply a walled-in room.

That’s a fair amount of groundwork for the code, but nothing is visible yet!

To display the maze data and verify how it looks, add the following method to MazeConstructor:

void OnGUI()
{
    //1
    if (!showDebug)
    {
        return;
    }

    //2
    int[,] maze = data;
    int rMax = maze.GetUpperBound(0);
    int cMax = maze.GetUpperBound(1);

    string msg = "";

    //3
    for (int i = rMax; i >= 0; i--)
    {
        for (int j = 0; j <= cMax; j++)
        {
            if (maze[i, j] == 0)
            {
                msg += "....";
            }
            else
            {
                msg += "==";
            }
        }
        msg += "\n";
    }

    //4
    GUI.Label(new Rect(20, 20, 500, 500), msg);
}

Taking each commented section in turn:

  1. This code checks if debug displays are enabled.
  2. Initialize several local variables: a local copy of the stored maze, the maximum row and column, and a string to build up.
  3. Two nested loops iterate over the rows and columns of the two-dimensional array. For each row/column of the array, the code checks the stored value and appends either "...." or "==" depending on if the value is zero. The code also appends a newline after iterating through all the columns in a row, so that each row is a new line.
  4. Finally, GUI.Label() prints out the built-up string. This project uses the newer GUI system for displays seen by the player, but the older system is simpler for creating quick debug displays.

Remember to turn on Show Debug on the MazeConstructor component. Hit Play, and the stored maze data (which is just the default maze for now) will be displayed:

Default procedural mazes

Displaying the stored data is a good start! However, the code isn't actually generating a maze yet. The next section explains how to handle that task.

Generating the Maze Data

Note that MazeConstructor.GenerateNewMaze() is currently empty; for now it's simply a stub to fill in later. In the GameController script, add the following line to the end of the Start() method. This will call that stub method:

    generator.GenerateNewMaze(13, 15);

The "magic" numbers 13 and 15 in the method's parameters dictate how large to make the maze. While they aren't being used quite yet, these size parameters determine the number of rows and columns in the grid respectively.

At this point you can start generating the data for the maze. Create a new script named MazeDataGenerator; this class will encapsulate the data generation logic, and will be used by MazeConstructor. Open the new script and replace everything with:

using System.Collections.Generic;
using UnityEngine;

public class MazeDataGenerator
{
    public float placementThreshold;    // chance of empty space

    public MazeDataGenerator()
    {
        placementThreshold = .1f;                               // 1
    }

    public int[,] FromDimensions(int sizeRows, int sizeCols)    // 2
    {
        int[,] maze = new int[sizeRows, sizeCols];
        // stub to fill in
        return maze;
    }
}

Note that this class doesn't inherit from MonoBehaviour. It won't be directly used as a component, only from within MazeConstructor, so it doesn't need MonoBehaviour's functionality.

Meanwhile:

  1. placementThreshold will be used by the data generation algorithm to determine whether a space is empty. This variable is assigned a default value in the class constructor, but it's made public so that other code can tune the generated maze.
  2. Once again, one method (FromDimensions() in this case) is currently simply a stub to call and will be filled in shortly.

Next add some sections of code to MazeConstructor to enable it to call the stub method. First add a private variable to store the data generator:

private MazeDataGenerator dataGenerator;

Then instantiate it in Awake(), storing the generator in the new variable by adding the following line to the top of the Awake() method.

    dataGenerator = new MazeDataGenerator();

Finally, call FromDimensions() in GenerateNewMaze(), passing along the grid size and storing the resulting data. Find the line containing // stub to fill in in GenerateNewMaze() and replace it with the following:

    if (sizeRows % 2 == 0 && sizeCols % 2 == 0)
    {
        Debug.LogError("Odd numbers work better for dungeon size.");
    }

    data = dataGenerator.FromDimensions(sizeRows, sizeCols);

Note the warning about odd numbers working better for size; that's because the generated maze will be surrounded by walls.

Play the game to see the correctly sized but otherwise empty maze data:

Empty maze

Great! Everything's in place to store and display the maze data! Time to implement the maze generation algorithm inside FromDimensions().

The algorithm described earlier iterates over every other space in the grid (no, not every single space!) to both place a wall and choose an adjacent space to block as well. The algorithm programmed here is a slight modification that also decides if the space should simply be skipped, resulting in open spaces to vary the maze. Since the algorithm doesn't need to store much, or know anything about the rest of the maze, such as a list of branch points to iterate over, the code becomes very simple.

To implement this maze generation algorithm, add the following code to FromDimensions() in MazeDataGenerator replacing the line that reads // stub to fill in.

    int rMax = maze.GetUpperBound(0);
    int cMax = maze.GetUpperBound(1);

    for (int i = 0; i <= rMax; i++)
    {
        for (int j = 0; j <= cMax; j++)
        {
            //1
            if (i == 0 || j == 0 || i == rMax || j == cMax)
            {
                maze[i, j] = 1;
            }

            //2
            else if (i % 2 == 0 && j % 2 == 0)
            {
                if (Random.value > placementThreshold)
                {
                    //3
                    maze[i, j] = 1;

                    int a = Random.value < .5 ? 0 : (Random.value < .5 ? -1 : 1);
                    int b = a != 0 ? 0 : (Random.value < .5 ? -1 : 1);
                    maze[i+a, j+b] = 1;
                }
            }
        }
    }

As you can see, the code gets the boundaries of the 2D array and then iterates through it:

  1. For every grid cell, the code first checks if the current cell is on the outside of the grid (that is, if either index is on the array boundaries). If so, assign 1 for wall.
  2. The code next checks if the coordinates are evenly divisible by 2 in order to operate on every other cell. There is a further check against the placementThreshold value described earlier, to randomly skip this cell and continue iterating through the array.
  3. Finally, the code assigns 1 to both the current cell and a randomly chosen adjacent cell. The code uses a series of ternary operators to randomly add 0, 1, or -1 to the array index, thereby getting the index of an adjacent cell.

Display the maze data again in order to verify how the generated maze looks:

Maze data

Restart the game to see that the maze data is different each time. Pretty cool!

The next big task is to generate a 3D mesh from the 2D maze data.

Generating the Maze Mesh

Now that the underlying data for the maze is being generated properly, you can construct the mesh based on that data.

Create another new script named MazeMeshGenerator. Much like MazeDataGenerator encapsulated the data generation logic, MazeMeshGenerator will contain the mesh generation logic and will be used by MazeConstructor to handle that step in generating the maze.

Or rather, it will eventually contain the mesh generation logic. First, you'll simply create a textured quad for demonstration purposes and then modify that code to generate the entire maze. In order to do that, you'll need to make a few minor adjustments within Unity's editor before diving into the code.

First, you need to link the materials that will be applied to the generated mesh.

Select the Graphics folder down in the Project window, then select Controller up in the Hierarchy window in order to see its Maze Constructor component in the Inspector.

Drag the materials from the Graphics folder over to the material slots in Maze Constructor. Use floor-mat for Material 1 and wall-mat for Material 2, while start and treasure go in the according slots.

Since you're already working in the Inspector, also add a tag called Generated: click on the Tag menu at the top of the Inspector and select Add Tag. When you generate meshes, you'll assign that tag in order to identify them.

Now that the needed adjustments have all been made in Unity's editor, open the new script and replace everything with:

using System.Collections.Generic;
using UnityEngine;

public class MazeMeshGenerator
{    
    // generator params
    public float width;     // how wide are hallways
    public float height;    // how tall are hallways

    public MazeMeshGenerator()
    {
        width = 3.75f;
        height = 3.5f;
    }

    public Mesh FromData(int[,] data)
    {
        Mesh maze = new Mesh();

        //1
        List<Vector3> newVertices = new List<Vector3>();
        List<Vector2> newUVs = new List<Vector2>();
        List<int> newTriangles = new List<int>();
        
        // corners of quad
        Vector3 vert1 = new Vector3(-.5f, -.5f, 0);
        Vector3 vert2 = new Vector3(-.5f, .5f, 0);
        Vector3 vert3 = new Vector3(.5f, .5f, 0);
        Vector3 vert4 = new Vector3(.5f, -.5f, 0);

        //2
        newVertices.Add(vert1);
        newVertices.Add(vert2);
        newVertices.Add(vert3);
        newVertices.Add(vert4);

        //3
        newUVs.Add(new Vector2(1, 0));
        newUVs.Add(new Vector2(1, 1));
        newUVs.Add(new Vector2(0, 1));
        newUVs.Add(new Vector2(0, 0));

        //4
        newTriangles.Add(2);
        newTriangles.Add(1);
        newTriangles.Add(0);

        //5
        newTriangles.Add(3);
        newTriangles.Add(2);
        newTriangles.Add(0);

        maze.vertices = newVertices.ToArray();
        maze.uv = newUVs.ToArray();
        maze.triangles = newTriangles.ToArray();

        return maze;
    }
}

The two fields at the top of the class, width and height, are just like placementThreshold from MazeDataGenerator: values, set with to default in the constructor, that are used by the mesh generation code.

The majority of the interesting code is inside FromData(); that's the method MazeConstructor calls to generate a mesh. At the moment the code just creates a single quad, in order to demonstrate how that works. You'll expand that to an entire level shortly.

This illustration shows what a quad is made of:

This code is long but fairly repetitive, with minor variations:

  1. A mesh is comprised of three lists: the vertices, the UV coordinates and the triangles.
  2. The list of vertices stores the position of each vertex...
  3. The UV coordinates listed go with the corresponding vertex in that list...
  4. And the triangles are indexes in the list of vertices (i.e. "this triangle is made from vertices 0, 1, and 2").
  5. Note that two triangles were created; a quad is made of two triangles. Note also that List data types were used (in order to append to the list) but ultimately Mesh needs Arrays.

MazeConstructor needs to instantiate MazeMeshGenerator and then call the mesh generation method. Meanwhile it also needs to display the mesh, so here are the pieces of code to add:

First add a private field to store the mesh generator.

private MazeMeshGenerator meshGenerator;

Instantiate it in Awake(), storing the mesh generator in the new field by adding the following line to the top of the Awake() method:

    meshGenerator = new MazeMeshGenerator();

Next add the DisplayMaze() method:

private void DisplayMaze()
{
    GameObject go = new GameObject();
    go.transform.position = Vector3.zero;
    go.name = "Procedural Maze";
    go.tag = "Generated";

    MeshFilter mf = go.AddComponent<MeshFilter>();
    mf.mesh = meshGenerator.FromData(data);
    
    MeshCollider mc = go.AddComponent<MeshCollider>();
    mc.sharedMesh = mf.mesh;

    MeshRenderer mr = go.AddComponent<MeshRenderer>();
    mr.materials = new Material[2] {mazeMat1, mazeMat2};
}

Finally, in order to call DisplayMaze(), add the following line to the end of GenerateNewMaze():

    DisplayMaze();

By itself, a Mesh is simply data. It isn't visible until assigned to an object (more specifically, the object's MeshFilter) in the scene. Thus DisplayMaze() doesn't only call MazeMeshGenerator.FromData(), but rather inserts that call in the middle of instantiating a new GameObject, setting the Generated tag, adding MeshFilter and the generated mesh, adding MeshCollider for colliding with the maze, and finally adding MeshRenderer and materials.

Having programmed the MazeMeshGenerator class, and instantiated it in MazeConstructor, hit Play now:

Single quad

You’ve built a textured quad completely from code! That's an exciting and important beginning, so pause here to review your work up to this point if you don't completely understand how it works.

Next up is a rather significant refactor of FromData(); replace it entirely with:

public Mesh FromData(int[,] data)
{
    Mesh maze = new Mesh();

    //3
    List<Vector3> newVertices = new List<Vector3>();
    List<Vector2> newUVs = new List<Vector2>();

    maze.subMeshCount = 2;
    List<int> floorTriangles = new List<int>();
    List<int> wallTriangles = new List<int>();

    int rMax = data.GetUpperBound(0);
    int cMax = data.GetUpperBound(1);
    float halfH = height * .5f;

    //4
    for (int i = 0; i <= rMax; i++)
    {
        for (int j = 0; j <= cMax; j++)
        {
            if (data[i, j] != 1)
            {
                // floor
                AddQuad(Matrix4x4.TRS(
                    new Vector3(j * width, 0, i * width),
                    Quaternion.LookRotation(Vector3.up),
                    new Vector3(width, width, 1)
                ), ref newVertices, ref newUVs, ref floorTriangles);

                // ceiling
                AddQuad(Matrix4x4.TRS(
                    new Vector3(j * width, height, i * width),
                    Quaternion.LookRotation(Vector3.down),
                    new Vector3(width, width, 1)
                ), ref newVertices, ref newUVs, ref floorTriangles);


                // walls on sides next to blocked grid cells

                if (i - 1 < 0 || data[i-1, j] == 1)
                {
                    AddQuad(Matrix4x4.TRS(
                        new Vector3(j * width, halfH, (i-.5f) * width),
                        Quaternion.LookRotation(Vector3.forward),
                        new Vector3(width, height, 1)
                    ), ref newVertices, ref newUVs, ref wallTriangles);
                }

                if (j + 1 > cMax || data[i, j+1] == 1)
                {
                    AddQuad(Matrix4x4.TRS(
                        new Vector3((j+.5f) * width, halfH, i * width),
                        Quaternion.LookRotation(Vector3.left),
                        new Vector3(width, height, 1)
                    ), ref newVertices, ref newUVs, ref wallTriangles);
                }

                if (j - 1 < 0 || data[i, j-1] == 1)
                {
                    AddQuad(Matrix4x4.TRS(
                        new Vector3((j-.5f) * width, halfH, i * width),
                        Quaternion.LookRotation(Vector3.right),
                        new Vector3(width, height, 1)
                    ), ref newVertices, ref newUVs, ref wallTriangles);
                }

                if (i + 1 > rMax || data[i+1, j] == 1)
                {
                    AddQuad(Matrix4x4.TRS(
                        new Vector3(j * width, halfH, (i+.5f) * width),
                        Quaternion.LookRotation(Vector3.back),
                        new Vector3(width, height, 1)
                    ), ref newVertices, ref newUVs, ref wallTriangles);
                }
            }
        }
    }

    maze.vertices = newVertices.ToArray();
    maze.uv = newUVs.ToArray();
    
    maze.SetTriangles(floorTriangles.ToArray(), 0);
    maze.SetTriangles(wallTriangles.ToArray(), 1);

    //5
    maze.RecalculateNormals();

    return maze;
}

//1, 2
private void AddQuad(Matrix4x4 matrix, ref List<Vector3> newVertices,
    ref List<Vector2> newUVs, ref List<int> newTriangles)
{
    int index = newVertices.Count;

    // corners before transforming
    Vector3 vert1 = new Vector3(-.5f, -.5f, 0);
    Vector3 vert2 = new Vector3(-.5f, .5f, 0);
    Vector3 vert3 = new Vector3(.5f, .5f, 0);
    Vector3 vert4 = new Vector3(.5f, -.5f, 0);

    newVertices.Add(matrix.MultiplyPoint3x4(vert1));
    newVertices.Add(matrix.MultiplyPoint3x4(vert2));
    newVertices.Add(matrix.MultiplyPoint3x4(vert3));
    newVertices.Add(matrix.MultiplyPoint3x4(vert4));

    newUVs.Add(new Vector2(1, 0));
    newUVs.Add(new Vector2(1, 1));
    newUVs.Add(new Vector2(0, 1));
    newUVs.Add(new Vector2(0, 0));

    newTriangles.Add(index+2);
    newTriangles.Add(index+1);
    newTriangles.Add(index);

    newTriangles.Add(index+3);
    newTriangles.Add(index+2);
    newTriangles.Add(index);
}

Whew — that last batch of code was long! But again, it’s pretty much the same thing over and over with a few numbers changed. In particular, the code to generate a quad was moved into a separate AddQuad() method in order to call that repeatedly for the floor, ceiling, and walls of each grid cell.

  1. The final three parameters to AddQuad() are the same list of vertices, UVs, and triangles to append to. Meanwhile, the first line of the method gets the index to start from; as more quads are appended, the index will increase.
  2. However, the first AddQuad() parameter is a transformation matrix, and that part may be confusing. Essentially, a position/rotation/scale can be stored in a matrix, and then applied to the vertices. That's what the MultiplyPoint3x4() calls are doing. That way, the exact same code to generate a quad can be used for floors, walls, etc. You only need to vary the transformation matrix being used!
  3. Going back to FromData(), lists for vertices, UVs, and triangles are created at the top. This time, there are two lists of triangles. Unity's Mesh object can have multiple sub-meshes with a different material on each, so each list of triangles is a different sub-mesh. You declare two sub-meshes so that you can assign different materials to the floor and walls.
  4. After that, you iterate through the 2D array and build quads for floor, ceiling, and walls at every grid cell. While every cell needs a floor and ceiling, there are checks of adjacent cells to see which walls are needed. Note how AddQuad() is being called repeatedly, but with a different transform matrix each time, and with different triangle lists used for floors and walls. Also note that width and height are used to determine where quads are positioned and how big they are.
  5. Oh and one other subtle addition: RecalculateNormals() prepares the mesh for lighting.

Press Play to see the full maze mesh generated:

Game & Scene views

Congratulations; that was the entirety of maze generation, and the majority of the programming needed for Speedy Treasure Thief! You'll wrap up the rest of the game in the next section.

Finishing the Game

You need to make some more additions and changes in the code, but first let's go over what was provided by the starter package. As mentioned in the introduction, the starter package included two scripts, a scene with the player and UI, and all the graphics for the maze game. The FpsMovement script is simply a one-script version of the character controller from my book, while TriggerEventRouter is a utility that's handy for triggers in the game.

The scene has the player already set up, including an FpsMovement component and a spotlight attached to the camera. The skybox and environment lighting are also turned off in the Lighting Settings window. Finally, the scene has a UI canvas, with labels for score and time already placed.

That summarizes what the starter package provided. Now you’ll write the remaining code for this game.

First up is MazeConstructor. First, add the following properties to store sizes and coordinates:

public float hallWidth
{
    get; private set;
}
public float hallHeight
{
    get; private set;
}

public int startRow
{
    get; private set;
}
public int startCol
{
    get; private set;
}

public int goalRow
{
    get; private set;
}
public int goalCol
{
    get; private set;
}

Now to add some new methods. The first one is DisposeOldMaze(); as the name implies, this deletes any existing maze. The code finds all objects with the Generated tag and destroys them.

public void DisposeOldMaze()
{
    GameObject[] objects = GameObject.FindGameObjectsWithTag("Generated");
    foreach (GameObject go in objects) {
        Destroy(go);
    }
}

The next method to add is FindStartPosition(). This code starts at 0,0 and iterates through the maze data until it finds an open space. Then those coordinates are stored as the maze's start position.

private void FindStartPosition()
{
    int[,] maze = data;
    int rMax = maze.GetUpperBound(0);
    int cMax = maze.GetUpperBound(1);

    for (int i = 0; i <= rMax; i++)
    {
        for (int j = 0; j <= cMax; j++)
        {
            if (maze[i, j] == 0)
            {
                startRow = i;
                startCol = j;
                return;
            }
        }
    }
}

Similarly, FindGoalPosition() does essentially the same thing, only starting with max values and counting down. Add this method as well.

private void FindGoalPosition()
{
    int[,] maze = data;
    int rMax = maze.GetUpperBound(0);
    int cMax = maze.GetUpperBound(1);

    // loop top to bottom, right to left
    for (int i = rMax; i >= 0; i--)
    {
        for (int j = cMax; j >= 0; j--)
        {
            if (maze[i, j] == 0)
            {
                goalRow = i;
                goalCol = j;
                return;
            }
        }
    }
}

PlaceStartTrigger() and PlaceGoalTrigger() place objects in the scene at the start and goal positions. Their collider is set to be a trigger, the appropriate material is applied, and then TriggerEventRouter (from the starter package) is added. This component takes a callback function, to call when something enters the trigger volume. Add these two methods as well.

private void PlaceStartTrigger(TriggerEventHandler callback)
{
    GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
    go.transform.position = new Vector3(startCol * hallWidth, .5f, startRow * hallWidth);
    go.name = "Start Trigger";
    go.tag = "Generated";

    go.GetComponent<BoxCollider>().isTrigger = true;
    go.GetComponent<MeshRenderer>().sharedMaterial = startMat;

    TriggerEventRouter tc = go.AddComponent<TriggerEventRouter>();
    tc.callback = callback;
}

private void PlaceGoalTrigger(TriggerEventHandler callback)
{
    GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
    go.transform.position = new Vector3(goalCol * hallWidth, .5f, goalRow * hallWidth);
    go.name = "Treasure";
    go.tag = "Generated";

    go.GetComponent<BoxCollider>().isTrigger = true;
    go.GetComponent<MeshRenderer>().sharedMaterial = treasureMat;

    TriggerEventRouter tc = go.AddComponent<TriggerEventRouter>();
    tc.callback = callback;
}

Finally replace the entire GenerateNewMaze() method with the following:

public void GenerateNewMaze(int sizeRows, int sizeCols,
    TriggerEventHandler startCallback=null, TriggerEventHandler goalCallback=null)
{
    if (sizeRows % 2 == 0 && sizeCols % 2 == 0)
    {
        Debug.LogError("Odd numbers work better for dungeon size.");
    }

    DisposeOldMaze();

    data = dataGenerator.FromDimensions(sizeRows, sizeCols);

    FindStartPosition();
    FindGoalPosition();

    // store values used to generate this mesh
    hallWidth = meshGenerator.width;
    hallHeight = meshGenerator.height;

    DisplayMaze();

    PlaceStartTrigger(startCallback);
    PlaceGoalTrigger(goalCallback);
}

The rewritten GenerateNewMaze() calls the new methods you just added for things like disposing the old mesh and placing triggers.

You've added a lot to MazeConstructor! Well done. Fortunately you’re done with that class now. Just one more set of code to go.

Now add some additional code in GameController. Replace the entire contents of the file with the following:

using System;
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(MazeConstructor))]

public class GameController : MonoBehaviour
{
    //1
    [SerializeField] private FpsMovement player;
    [SerializeField] private Text timeLabel;
    [SerializeField] private Text scoreLabel;

    private MazeConstructor generator;

    //2
    private DateTime startTime;
    private int timeLimit;
    private int reduceLimitBy;

    private int score;
    private bool goalReached;

    //3
    void Start() {
        generator = GetComponent<MazeConstructor>();
        StartNewGame();
    }

    //4
    private void StartNewGame()
    {
        timeLimit = 80;
        reduceLimitBy = 5;
        startTime = DateTime.Now;

        score = 0;
        scoreLabel.text = score.ToString();

        StartNewMaze();
    }

    //5
    private void StartNewMaze()
    {
        generator.GenerateNewMaze(13, 15, OnStartTrigger, OnGoalTrigger);

        float x = generator.startCol * generator.hallWidth;
        float y = 1;
        float z = generator.startRow * generator.hallWidth;
        player.transform.position = new Vector3(x, y, z);

        goalReached = false;
        player.enabled = true;

        // restart timer
        timeLimit -= reduceLimitBy;
        startTime = DateTime.Now;
    }

    //6
    void Update()
    {
        if (!player.enabled)
        {
            return;
        }

        int timeUsed = (int)(DateTime.Now - startTime).TotalSeconds;
        int timeLeft = timeLimit - timeUsed;

        if (timeLeft > 0)
        {
            timeLabel.text = timeLeft.ToString();
        }
        else
        {
            timeLabel.text = "TIME UP";
            player.enabled = false;

            Invoke("StartNewGame", 4);
        }
    }

    //7
    private void OnGoalTrigger(GameObject trigger, GameObject other)
    {
        Debug.Log("Goal!");
        goalReached = true;

        score += 1;
        scoreLabel.text = score.ToString();

        Destroy(trigger);
    }

    private void OnStartTrigger(GameObject trigger, GameObject other)
    {
        if (goalReached)
        {
            Debug.Log("Finish!");
            player.enabled = false;

            Invoke("StartNewMaze", 4);
        }
    }
}
  1. The first things added are serialized fields for objects in the scene.
  2. Several private variables were added to keep track of the game's timer, score, and if the maze's goal was found yet.
  3. MazeConstructor is initialized just like before, but now Start() uses new methods that do more than just calling GenerateNewMaze().
  4. StartNewGame() is used to start the entire game from the beginning, as opposed to switching levels within a game. The timer is set to starting values, score is reset, and then a maze is created.
  5. StartNewMaze() progresses to next level without starting the entire game over. Besides generating a new maze, this method places the player at the start, resets the goal, and reduces the time limit.
  6. Update() checks if the player is active, and then updates time remaining to complete the level. Once time is up, the player is deactivated and a new game is started.
  7. OnGoalTrigger() and OnStartTrigger() are callbacks passed to TriggerEventRouter in MazeConstructor. OnGoalTrigger() records that the goal was found, and then increments the score. OnStartTrigger() checks if the goal was found, then deactivates the player and starts a new maze.

That's all of the code. Turn your attention back to the scene in Unity. First, select the Canvas in the Hierarchy window and enable it in the Inspector. The Canvas was turned off so as not to interfere with the debug display while building the maze code. Remember that serialized fields were added, so drop those scene objects (Player, Time label on Canvas, and Score label) onto the slots in the Inspector. You probably also want to turn off Show Debug, then hit Play:

Speedy Treasure Thief

Great job! Procedurally generating mazes can be tricky, but they result in engaging and dynamic gameplay.

Where To Go From Here?

If you've been following along, you should have created the complete game by now. If you want, feel free to download the finished Unity project from this tutorial here.

Going forward, you could explore other maze generation algorithms, replacing the code in FromDimensions(). You could also try generating other environments; start by looking up cave generation using cellular automata.
Randomly generating items and enemies around the map may also prove to be a lot of fun!

I hope you enjoyed this tutorial, and if you have any questions or comments, please join the discussion below!

The post Procedural Generation Of Mazes With Unity appeared first on Ray Wenderlich.

Video Tutorial: Beginning iOS Debugging Part 8: View Hierarchy

Video Tutorial: Beginning iOS Debugging Part 9: Challenge: Putting It Together


How To Keep Learning After 50 and Other Ways to Hack Your Brain at any Age

$
0
0

“You can’t teach an old dogs new tricks!” You may have heard this saying. You may also think that it means that older you get, the harder it is to learn new skills. By extension, that means learning after 50 is difficult.

That isn’t really what the saying means. It is trying to express that we, as human beings, get used to doing things in set ways.

I asked some “old dogs” to weigh in on their own learning now that they are 50 and better. When it comes to learning new things, all of the folks I asked have no problem learning new things that interest them. You could even say learning keeps them young — there is science to back this up. When we get older, it’s not our minds that betray us, rather, it’s our bodies and those set routines. Ask anyone you know how old they feel, and it’s likely they’ll say they are still a teenager — in their head anyways.

Think about how fascinating it is to watch a baby start as a cooing little bundle, become a toddler, and eventually grow into a little person. All within a few short years. Everyday, new skills pile on top of yesterday’s skills until the child is running out the door.

How is it that we continue to pick up new skills over our lifetime? It’s a continual desire to learn, and it’s more similar to how a baby grows into a little person than you might think.

“Perseverance is the key” — my teammate, Antonio Bello.

Spoiler Alert: Your brain will change by reading this article.

Research for this article involved surveying over 25 people, more if you count interactions on social networks, to see what they had been up to. Many of the folks have been at it for over 50 years! I surveyed men and women, as well as artists, coaches and several people who write code daily — after all, this is a tech blog focused on mobile development.

They conveyed how far back this need to learn, and even the desire to tame a computer, went for them.

Over half of the participants are employed, with others working freelance or otherwise self-employed. There was an even split between those who studied computer science and liberal arts — truly at that famous intersection that Steve Jobs mentioned. Half of the respondents call themselves coders, and the majority identify as software developers.

Participants are mostly Mac and iPhone users, but Windows users are well represented. Amazingly, the majority have used Fortran and Objective-C followed by an equal amount of Pascal and Swift. It is clear that this group has been writing code for a very long time.

From Fortran to Swift

I asked each participant how they keep up, and how they keep sharp. The most common theme was that they take on new challenges. They read. They keep up with technology. They’re learning after 50 by doing puzzles, watching online video courses, and learning by doing — lots of hands-on experimentation and play.

“I think that the secret to learning anything at any age, really, is an insatiable curiosity about ‘whatever’ — could be one topic, or many topics depending on if you are a multi-tasker or not.”
– Elaine Manganello

Early scientific theories suggested that the brain only developed during early childhood, and once developed, the brain could not be changed. By that reasoning, it would be difficult to teach that old dog a new trick.

Studies of the brain In the last half of the 20th century, in contrast to the old theories, found that the brain develops throughout a person’s life. Pathways in the brain continue to form — regardless of age.

Memories form when synapses are created as electrical signals jump across the brain’s neurons. Repetition makes these synapses stronger over time. This activity continues throughout life, and the brain changes over and over. New skills, memories and capabilities grow — along with bad habits, and sometimes, addictions.

“One Word: Plastics” — The Gradulate 1967

Neuroplastcity is the name given to this science of “plastic” brain pathway construction.

Even after considerable brain trauma and physical injury, the brain is able to adapt and create fresh pathways. In the case of brain damage, it has the ability to move to different areas — a process known as cortical remapping.

This plastic nature explains why we can continue to learn and adopt new skills, including learning new languages and even picking up new musical instruments. This makes sense in the context of learning after 50 — the participants surveyed all stated that they have pursued life-long learning, an excellent example being that many moved from Fortran and Pascal to learn Swift and write iOS apps.

Dr. Josh Turknett, a neurologist, musician, and neuroplastician, explains how people can “hack” their brains. We can do this by re-examining how the brain forms new connections while we’re learning. He lays out nine steps that cover how to practice and learn to play an instrument, in his case, a banjo. The majority of people who learn to play an instrument make early progress; however, they also give up and abandon when progress seems to slow down.

How do you eat an elephant?

Solution Inside SelectShow>

Among Dr. Turknett’s interesting ideas is that you should break down what you learn into simple parts rather than attempt mastery all at once. As an analogy, he explains how babies learn to speak. They start by making vowel sounds, which are akin to “micro skills” that they pick up. They practice, and eventually move onto words and sentences.

Practice in moderation and reptition over time is key to learning after 50, or mastering skills at any age.

Changing Your Mind

Dr Lara Boyd, in her TEDxVancouver talk on neuroplasicity, laid down the three ways that the brain changes with chemical changes that support learning. The brain uses the chemicals that signal between neurons, as mentioned above, to create short-term memories. While short-term memories tend to mean great progress for people learning new skills, they also dissapate after a day or so.

Repeated practice is what allows people to start to make structural changes to their brains. With repetition, certain parts of the brain physically change and long-term memories form. It becomes easier to remember what was learned. Specialization then manifests in localized areas.

For example, Braile readers’ brain centers involved in hand and motor control are larger than that of somebody who reads with their eyes. Cab drivers’ spatial recognition areas grow large through the action of memorizing streets and addresses.

Structural changes lead to the third type of change…

And Now: Functional Programing

“The last way the brain can change is by altering is functional…” — Dr Boyd.

Functional change is where your brain uses several regions when you use a certain skill, each of which becomes excitable and easy to use repeatedly. This is sometimes called muscle memory.

Think about how once you grasp a concept it becomes simple to repeat it — like when your motor skills finally enable you to express your musical self on a new instrument, or you feel confidence in your skill with a new sport.

After a hockey practice, Wayne Gretzky would often stay on the ice with a bucket of pucks. He would shoot repeatedly at the same corner of the net. He was building structures and behaviors that he could recall when the opportunity to make that shot came up. Even if it only came up rarely in a real game, his brain would be prepared to take the shot, and if he made it, it would seem like magic to the fans. But wasn’t magic. Gretzky was hacking his brain to make functional changes.

Old Dogs Can Learn New Tricks

Of the those who participated in the survey, many expressed similar sentiments when asked how they stay relevant and learn new things. They talked about reading, watching videos, continually learning new things, and taking on new challenges.

Personally, I’m very intrigued at the thought of “hacking” my own brain. It’s fascinating to see how learning works and how changes in the brain aid in retaining long-term skills and mastering behaviors.

I can relate as a self-taught musician and a self-taught software developer. It does take time, practice and repetition to overcome what initially can be difficult skills to acquire. A great example is that after many years of jamming with a few friends, we actually became passable as a band. We did it by building on small things each week and creating those behaviors and muscle memory.

Eating An Elephant

In the world of software development, the same seems to be true. It’s hard (impossible?) to learn all of it at once, but you can learn various parts of an app, in small pieces, by going through the iOS tutorials on this site.

Break the task down into digestible pieces — as Dr. Turknet calls them, micro skills. Over time, they build up to an impressive repertoire of knowledge.

I know that like many of you, I can have a hard time grasping a concept. However, repeating a course, reading another book, looking at things from another perspective or watching an instructional video, are how I go about building an understanding of a new concept. Each time I look at it again, it becomes clearer.

Many of us old dogs continually learn to this day — hey, I learned about neuroplastcity writing this article!

One benefit of getting older is that you learn not to sweat the small stuff, like those times when you don’t grasp a concept right away. With this new knowledge, you can build the behavior you want, and now that you have an idea of how you learn, you can start to hack your own brain to learn new skills.

“Study how and what you learn best. Repeat those behaviors that are healthy for your brain and break those behaviors and habits that are not. Practice. Learning is about doing the work that your brain requires…

go out and build the brain you want.” – Dr Lara Boyd

Where to go from here?

I encourage you to look into neuroplasticity, how your own brain works, and how you learn. People tend to make great progress at first as chemical signals move around and neurons fire away, creating short-term memories. Then structural changes come with repetition and exercise. Finally, functional changes build up specialized areas of your brain.

Now you also know why you don’t retain things without practice and repetition — these lead to structural, and eventually, functional changes. They’re how you hack your brain and build those important long-term memories and neural pathways. They’re how you continue learning after 50.

Above all, work on the things that interest you — practice and persevere. When you get to be an old dog, or even if you’re an older dog than me, you’ll understand how your brain has transformed itself, and what you can do to transform it again.

You might enjoy reading our article Learning Techniques for Programmers, by Programmers.

Thanks to those who helped out on this article:
Bob, Phil Curry, Antonio Bello, Jack Cox, Colin Mackenzie, Mark Rubin, Elaine Manganello, Johanna Rothman, David Wasser, Gareth Burton, Aruna Mitra, Terry Brown, Eugene Knapik, Wendy Petcoff, Heath Freel, Charlene Winger, Irene Meitardjian, and Jean MacDonald

Sources:

Neuroplastcity – Wikipedia

The 9 Ways to Practice Smarter workshop (clawhammer and fingerstyle banjo)

After watching this, your brain will not be the same | Lara Boyd | TEDxVancouver

The post How To Keep Learning After 50 and Other Ways to Hack Your Brain at any Age appeared first on Ray Wenderlich.

Video Tutorial: Beginning iOS Debugging Part 10: Conclusion

TCP Server With the SwiftNIO Networking Framework

$
0
0

TCP Server With the SwiftNIO Networking Framework

Mobile developers often work with REST APIs or other networking protocols in their applications — whether it’s to retrieve data, communicate with other devices, or something else. SwiftNIO, written by Apple, is a low-level networking framework that makes writing Swift servers even easier than before, empowering Swift developers to leverage their skills on the server side.

In this tutorial, you’ll:

  • Learn about what purpose SwiftNIO serves and why Apple made and open-sourced it.
  • Practice working within a SwiftNIO framework by creating a Quote Of The Day Swift TCP server that you’ll connect to using a provided iOS app.

To start, you’ll need a Mac running at least macOS 10.12 Sierra with Xcode 9.3 or later. You’ll also use the command line, so be sure to open up Terminal, and check the command line utility for Swift is installed. You can check this by entering swift –version. It must be version 4.1 or later.

Note: The command line tool you need should be installed with Xcode. If you don’t successfully see your version of Swift when running the swift –version command, go here for more information.

Getting Started

First, it’s helpful to understand exactly how the SwiftNIO framework differs from other Swift frameworks.

Swift-on-the-Server Status Quo

If you’re a Swift developer, it’s very likely you’ve focused only on mobile applications. This makes servers in the cloud seem like voodoo magic — or at least a little confusing at first.

SwiftNIO

I don’t get you, cloud.

Thankfully, if you know Swift, frameworks like Kitura and Vapor make writing a web service easier.

However, these frameworks operate as a convenient layer on top of some sophisticated and low-level socket networking code, much of which interoperates with C. In the case of Kitura, once Swift became open sourced and available for Linux, IBM wrote BlueSocket to serve as its low-level socket networking layer.

How SwiftNIO Differs
SwiftNIO

You complete me, NIO.

BlueSocket is great, but it doesn’t fill in all the blanks when it comes to server-side Swift. For example, Kitura not only included the work of BlueSocket, but it also implemented the entire HTTP stack under the hood, so incoming communication would be routed the right way. Now, Apple has raised the bar, introducing SwiftNIO and essentially handling socket communication and HTTP for us.

Note: SwiftNIO has been written as a port of Netty, a Java low-level networking framework, but in Swift.

This is a diagram taken from Norman Maurer’s talk on SwiftNIO, given March 1, 2018, in Tokyo.

This tutorial won’t go into great detail about every piece of the above diagram but, as you work your way through, each piece should begin to make sense. However, it’s worth explaining what an EventLoopGroup is and, thus, what a MultiThreadedEventLoopGroup accomplishes with this class.

EventLoopGroup and MultiThreadedEventLoopGroup

An EventLoop runs in a loop, looking for new tasks to handle that have come in from a new client via a network connection. Think of an EventLoop like a serial DispatchQueue, which allows you to delegate blocks of code for execution at a time of your choosing. As multiple EventLoop instances cycle through your thread, they look for tasks to execute.

After an EventLoop searches for tasks and schedules them, it executes them one by one.

When you put these EventLoop instances into an EventLoopGroup, the handler looks more like a concurrent DispatchQueue. You can also think of this like a thread pool. This means you can submit multiple tasks to the group and, depending on the time it takes to complete each task, the order of completed tasks may not be what you initially submitted to the group.

This is where the MultiThreadedEventLoopGroup class comes in. This specifies the EventLoopGroup that’s created will tie each group to a specific thread, further streamlining your asynchronous operations that come in. Think of it like an upgraded EventLoopGroup.

Each task gets taken care of in its own discrete group.

Synchronous/Asynchronous Example

Let’s say you’re at a food truck operated by one person. The person at the front of the line orders his food. He pays; he waits. He gets his food. The next person repeats this same process. The food truck delivers everyone’s order correctly, but it’s very slow. If you’re at the end of the line, you are pretty unhappy.

This is an example of a synchronous operation — something that blocks all other work until the current request is completed. A connection to a PostgreSQL database is another example.

Now, imagine the same food truck has an order-taker and two chefs. The person at the front of the line orders his food. He pays; he waits. But wait! The second person can now order his food without having to wait for the operator to complete the order for the first person. And the first person only has to wait for one other person. Here, the chefs are the EventLoopGroups making the food.

This is an example of a set of asynchronous operations. Ultimately, you’re waiting on the available resources of the service. But this new setup can handle multiple requests at the same time. The end user will see an increase in performance.

Another example of this is… well, SwiftNIO!

Setting Up Quote of the Day

To kick things off, download the materials for this tutorial; you can find a link at the top or bottom of this tutorial. You’ll be implementing a server called Quote of the Day — believe it or not, Quote of the Day (QOTD) is a real internet standard (RFC). You can read the protocol spec here.

The flow of information from the server works as follows:

  • Open a TCP connection upon request.
  • Write a random quote to the response.
  • Send the response.
  • Close the connection.

If you take a look at the RFC for Quote of the Day, you’ll notice two key things that you’ll do differently in this tutorial:

  1. You’ll use port 1717 instead of 17. Port 17 is locked down, requiring root permissions on macOS.
  2. You’ll only create a TCP/IP connection, not a UDP connection. As of this writing, UDP is still a work-in-progress on SwiftNIO (so many acronyms!).

For this tutorial, you’ll also use an iOS client to check that the server works properly. This iOS client uses BlueSocket — you won’t be making any changes to the iOS app, but feel free to look through the source code on your own.

Note: SwiftNIO also has the ability to act as a client for network communication and not just as a server. This means you could, in theory, write an iOS client using SwiftNIO, too.

Running the iOS Client

In the starter project, go to the Mobile/ directory and open up the swift-nio-qotd-client.xcodeproj file. In Xcode, run this on a simulator of your choosing. However, don’t run this on a device, as you won’t easily be able to connect with a local server connection on your machine from your device.

When the simulator boots up, tap the refresh icon in the upper right-hand corner of the screen. You should see the following error underneath the Quote heading:

Fear not! This is normal.

Keep this simulator open, but minimize it on your machine. You’ll come back to this later when you get your server up and running.

Setting Up Your Server Project

Open Terminal and navigate to the root directory of the starter project. Enter the following commands:

cd Server/
swift build
swift package generate-xcodeproj
open QOTD-NIO.xcodeproj
Note: If you have MacPorts installed on your machine you may have to use the following in place of swift build above: rm -rf .build && unset PKG_CONFIG_PATH && swift build

Once Xcode opens, go to the top of the window and, in the scheme selector, select QOTD-NIO and My Mac like so:

Build and run the project. At the bottom of the debugger, you should see a single printed message: This server will be… the one!!! If you see this, you’re ready to dive in.

Bootstrapping a Server

In Xcode, open QuoteServer.swift. This is where you’re going to set up your server to bind to a port.

Replace the code in this file with the following:

import Foundation
// 1
import NIO

enum QOTDError: Error {
  case invalidHost
  case invalidPort
}

public class QuoteServer {
  // 2
  private let group = MultiThreadedEventLoopGroup(numThreads: System.coreCount)
  private var host: String?
  private var port: Int?
  
  init(host: String, port: Int) {
    // 3
    self.host = host
    self.port = port
  } 
}

What this code is doing:

  1. You import the NIO module in this class — you won’t get far without it!
  2. You specify a MultiThreadedEventLoopGroup when you run your server, as you learned earlier. You can run as many threads as your system will allow you here — use the System API for this.
  3. You set the host and port the server will listen on. You could perform validation logic on your host and port here, if needed.

Now, you’ll add a private variable for an object that you need called a ServerBootstrap. Think of this class as a helper class that sets up a server for you to serve information that you delegate. Inside your class declaration, but below init(host:port:), add this:

private var serverBootstrap: ServerBootstrap {
  // 1
  return ServerBootstrap(group: group)
    .serverChannelOption(ChannelOptions.backlog, value: 256)
    // 2
    .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
    .childChannelInitializer { channel in
      // 3
      channel.pipeline.add(handler: BackPressureHandler()).then { v in
        // 4
        channel.pipeline.add(handler: QuoteHandler())
      }
    }
    // 5
    .childChannelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
    .childChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
    .childChannelOption(ChannelOptions.maxMessagesPerRead, value: 16)
    .childChannelOption(ChannelOptions.recvAllocator, value: AdaptiveRecvByteBufferAllocator())
}

Looking at the code above:

  1. You assign the bootstrap a MultiThreadedEventLoopGroup — it needs to know which run loops to utilize on your machine.
  2. You specify you want your channel — or your communication pipe — to connect with a networking socket available on your machine and to reuse the local address on your machine (REUSEADDR).
  3. Once communications come through the bootstrap, the channels that are the children of this bootstrap also need work to do — the BackPressureHandler() basically ensures that reading a new request only happens as fast as the server is writing a response.
  4. You then specify that you want to write your own custom class to handle incoming communication from the outside world — you’ll work on this class shortly.
  5. Finally, you specify that your server will operate on TCP and that you won’t delay the sending of any packets of information as a response.
Note: In the code block above, the explanation covers only the options that directly affect this tutorial. However, to learn more about the others, refer to the official Apple SwiftNIO Docs

Build and run your project. You should see the same result as before. You still have a few more steps to complete your working QOTD server.

Handling Incoming Requests With ChannelInboundHandler

In Xcode, open QuoteHandler.swift. Notice you already have a stubbed class that conforms to ChannelInboundHandler. This protocol gives you access to a series of methods that handle communication with the associated ServerBootstrap you set up. You’ll implement two of these shortly.

Notice the following two lines are already in the class:

public typealias InboundIn = ByteBuffer
public typealias OutboundOut = ByteBuffer

These are convenience typealiases that are required to declare to conform to ChannelInboundHandler so you can specify what kind of data comes into and out of your handler. You’re using ByteBuffer, which stores contiguously allocated raw bytes of data. This is straight-forward to work with when you’re writing a String to your response, as you’ll soon see.

Inside your class and underneath your typealias declarations, add the following:

// 1
func channelRegistered(ctx: ChannelHandlerContext) {
  print("Incoming connection registered - sending Quote of the Day")
  // 2
  let quote = QuoteGenerator.get()
  // 3
  var buffer = ctx.channel.allocator.buffer(capacity: quote.utf8.count)
  // 4
  buffer.write(string: quote)
  print("Sending quote")
  // 5
  ctx.writeAndFlush(self.wrapOutboundOut(buffer)).whenComplete {
    print("Closing connection")
    // 6
    ctx.close(promise: nil)
  }
}

// 7
public func errorCaught(ctx: ChannelHandlerContext, error: Error) {
  print("error: ", error)
  ctx.close(promise: nil)
}

Let’s take a look at what you’ve just added:

  1. channelRegistered is one of the methods in ChannelInboundHandler. Since you want to send a quote as a response when a connection is a requested, this method is quite convenient.
  2. You get a random quote from QuoteGenerator, a class that’s been provided for you that selects a quote from an array of them. Feel free to add any others you like.
  3. You get a handle for your response byte buffer from the incoming channel (communication pipe).
  4. You write your quote, which is a string, to the response buffer.
  5. Now that you’ve written to your buffer, you need to write the buffer to the channel, and then flush the communication pipe. Whatever you flush down the pipe is going to come out the other end, but this doesn’t guarantee the next line of code you write will wait for that to finish. This is where .whenComplete comes in.
  6. Inside your handler for .whenComplete, you’re establishing what you want to do when the previous operation is complete. In this case, to follow the requirements of the QOTD protocol, you close the channel connection.
  7. You also implement another method in ChannelInboundHandler for handling — you guessed it — errors! Notice you actually get a handy-dandy, Swifty Error type instead of something in C!

From this example, you can see that, anytime you want to handle incoming information from a client, you need to write a handler that conforms to ChannelInboundHandler to do so. From here, the possibilities are endless.

Polishing Off Your Server Functionality

In Xcode, go back to QuoteServer.swift and, in between your init(host:port:) method and your serverBootstrap var, add the following:

// 1
func run() throws {
  // 2
  guard let host = host else {
    throw QOTDError.invalidHost
  }
  guard let port = port else {
    throw QOTDError.invalidPort
  }
  do {
    // 3
    let channel = try serverBootstrap.bind(host: host, port: port).wait()
    print("\(channel.localAddress!) is now open")
    try channel.closeFuture.wait()
  } catch let error {
    throw error
  }
}

Breaking down what you’ve done:

  1. You don’t want to put this code inside init(host:port:) method because it’s not ideal to let code that runs “forever” reside in a constructor. This makes it easy to debug issues, too.
  2. You check for port and host using two guard statements.
  3. With values for host and port ensured, you bind the ServerBootstrap instance you create in this class to a host and port that you specify. Calling wait() at the end of this means you’re waiting for the “promise” to be fulfilled. This means you’re waiting for your server to start.

Poetic as it sounds, you can’t run forever; you need a graceful way to shut down your server. In the same class, after run(), add the following:

func shutdown() {
  do {
    // 1
    try group.syncShutdownGracefully()
  } catch let error {
    print("Could not shutdown gracefully - forcing exit (\(error.localizedDescription))")
    // 2
    exit(0)
  }
  print("Server closed")
}

Here’s what you’ve just added:

  1. You try the friendly API for the MultiThreadedEventLoopGroup for shutting down without causing any issues. This means it’ll wrap up any last tasks it might have to execute, and then it shuts itself down.
  2. Of course, if that just won’t fly, you hit the power button yourself and shut down the server “un-gracefully.”

Running Your Server

You’ve got yourself a server! Only one more change before we run it.

In Xcode, open main.swift. Delete the lone print statement at the bottom and replace it with the following:

// 1
let server = QuoteServer(host: "localhost", port: 1717)
do {
  // 2
  try server.run()
} catch let error {
  print("Error: \(error.localizedDescription)")
  // 3
  server.shutdown()
}

And here’s what this code does:

  1. You create an instance of your server, passing host and port.
  2. You run your server. Note, if this call works successfully, which it should, then the application will run infinitely until you shut it down or until something breaks.
  3. You check for any errors and, if you detect any, you make use of your handy-dandy shutdown() method.
Note: Remember you might not want to start your server from inside the constructor? The above code is a great example of this. A separate run() method allows us to make changes to other properties of your server before running it.

Build and run your project. You should see a line in the debugger about your server being open on port 1717.

Pull your iOS client back up. Build and run it if you stopped the project. Tap the refresh button and…

Voilà! A quote.

Your Swift TCP server is running! Check the logs on your Xcode project running your debugger and you should see the following output:

Incoming connection registered - sending Quote of the Day
Sending quote
Closing connection

Where to Go From Here?

You can download the final project using the link at the top or bottom of this tutorial. I encourage you to build on this and play with the available APIs to see what else you can do.

Additionally, if you clone the SwiftNIO project from source, there are a couple of demos you can try right inside the project itself. If you open Xcode, you can see the executables listed in the scheme selector:

Try running some of these executables either from Xcode or the command line.

For more information on SwiftNIO from Apple itself, check out the full Apple SwiftNIO Docs on Apple’s GitHub.

It’s an exciting time for Swift as it stretches past mobile development and into the rest of the programming world. Let us know what Swift projects you’re working on, your thoughts on the future of Swift, or leave us your favorite Quote of the Day in the forum discussion below!

The post TCP Server With the SwiftNIO Networking Framework appeared first on Ray Wenderlich.

Video Tutorial: Intermediate Debugging Part 1: Introduction

Video Tutorial: Intermediate Debugging Part 1: Breakpoint Improvements

Video Tutorial: Intermediate Debugging Part 1: Logging Using Breakpoints

New Course: Intermediate iOS Debugging

$
0
0

If you followed the Beginning iOS Debugging course, you’re ready to take your skills to the next level with more powerful debugging tools.

Today, we are releasing a brand new course: Intermediate iOS Debugging. In this course, you’ll learn more about using breakpoints, some tricks using LLDB (Xcode’s debugger), and some of the tools Xcode provides to help with debugging.

Take a look at what’s inside:

Part 1: Improved Breakpoints and Logging

In part one, pick up some more advanced breakpoint techniques.

  1. Introduction This introductory video will preview the course and introduce the debugging framework that will be used throughout.
  2. Breakpoint Improvements Create exception breakpoints to make finding the cause of an unhandled exception easier. See how to share breakpoints between projects or multiple users of a project.
  3. Logging Using Breakpoints Use breakpoints to add new logging without having to stop, add code, and recompile.
  4. Breaking in UIKit Learn how to use symbolic breakpoints to understand what’s going on in UIKit code. Add conditions to fine tune your breakpoints.
  5. Challenge: Logging Put your new-found skills to the test, logging what’s going on in framework code.
  6. Conclusion Review what you’ve learned in this section and see what’s coming up in Section 2.

Part 2: LLDB

In part two, tap into the power of the LLDB command line.

  1. Introduction In this video, see what’s coming up in the section and start learning about scope in LLDB.
  2. Inspecting Variables Learn about the three ways to inspect data in your app and the danger that comes with the power.
  3. Creating Variables Create new variables in LLDB and use them to make your debugging life easier.
  4. Challenge: Using Variables to Add Conditions Use LLDB variables between multiple breakpoints to make them more powerful.
  5. Changing UI See how to reflect changes in the UI without having to resume your app.
  6. Viewing Return Values Learn a new trick to view register values and use that to view the return value of a method.
  7. Breaking LLDB Use a flag to allow a method called from the LLDB command line to stop at breakpoints in that method.
  8. Conclusion Review what you’ve learned in this section and see what’s coming up in Section 3.

Part 3: Xcode

In the final part of the course, learn about some Xcode tools that can help you debug your apps.

  1. Introduction Preview the tools Xcode provides to simplify debugging.
  2. Application Language Use the scheme to make testing and switching between languages much easier and faster.
  3. Main Thread Checker Learn how to quickly spot issues where UIKit code is being called from a background thread.
  4. Malloc Stack Logging See how this tool helps pinpoint memory issues – taking you right to the line of code where a retained object was instantiated.
  5. Challenge: Viewing Properties from Debug Memory Graph In this challenge, use unsafeBitCast to view properties of objects in the debug memory graph. As a bonus, learn how to create command aliases.
  6. Runtime Issues See how Xcode finds and highlights issues with your app at runtime.
  7. Wireless Debugging Learn how easy it is to debug without having to tether your device to your Mac.
  8. Conclusion Review what you’ve learned in this course and see where to go next.

Where To Go From Here?

Want to check out the course? You can watch the first two videos for free!

The rest of the course is for raywenderlich.com subscribers only. Here’s how you can get access:

  • If you are a raywenderlich.com subscriber: The first three videos are ready for you today! The rest of the course will be released over this week. You can check out the course here.
  • If you are not a subscriber yet: What are you waiting for? Subscribe now to get access to our new Intermediate iOS Debugging course and our entire catalog of over 500 videos.

Stay tuned for more new and updated courses to come. I hope you enjoy the course! :]

The post New Course: Intermediate iOS Debugging appeared first on Ray Wenderlich.


SpriteKit Tutorial for Beginners

$
0
0
Update note: This SpriteKit tutorial has been updated by Brody Eller. The original post was written by Ray Wenderlich.

SpriteKit Tutorial for Beginners

Like Batman and Robin or Superman and Lois Lane, SpriteKit and Swift are an amazing combination:

  • SpriteKit is one of the best ways to make games on iOS. It’s easy to learn, powerful, and is fully supported by Apple.
  • Swift is an easy language to get started with, especially if you are a beginner to the iOS platform.

In this tutorial, you will learn how to create a simple 2D game using Apple’s 2D game framework, SpriteKit — using Swift!

You can either follow along with this SpriteKit tutorial, or just jump straight to the sample project at the end. And yes. There will be ninjas.

SpriteKit vs. Unity

The most popular alternative to SpriteKit at the moment is a game framework called Unity. Unity was originally developed as a 3D engine, but it has full built-in 2D support, too.

Before you get started, put some thought into whether SpriteKit or Unity is the best choice for your game.

Advantages of SpriteKit

  • It’s built right into iOS. There is no need to download extra libraries or have external dependencies. You can also seamlessly use other iOS APIs like iAd, In-App Purchases, etc. without having to rely on extra plugins.
  • It leverages your existing skills. If you already know Swift and iOS development, you can pick up SpriteKit extremely quickly.
  • It’s written by Apple. This gives you some confidence that it will be well supported moving forward on all of Apple’s new products. For example, you can use the same SpriteKit code to make your game work on iOS, macOS, and tvOS without a hitch.
  • It’s free. Maybe one of the best reasons for small indies! You get all of SpriteKit’s functionality at no cost. Unity does have a free version but it doesn’t have all of the features of the Pro version. You’ll need to upgrade if you want to avoid the Unity splash screen, for example.

Advantages of Unity

  • Cross-platform. This is one of the big ones. If you use SpriteKit, you’re locked into the Apple ecosystem. With Unity, you can easily port your games to Android, Windows, and more.
  • Visual scene designer. Unity makes it extremely easy to lay out your levels and test your game in realtime with the click of a button. SpriteKit does have a scene editor, but it is very basic compared to what Unity offers.
  • Asset store. Unity comes with a built-in asset store where you can buy various components for your game. Some of these components can save you a good bit of development time!
  • More powerful. In general, Unity just has more features and functionality than the SpriteKit/Scene Kit combination.

Which Should I Choose?

After this you may be thinking, “Well, which 2D framework should I choose?”

The answer depends on what your goals are:

  • If you’re a complete beginner, or solely focused on the Apple ecosystem: Use SpriteKit — it’s built-in, easy to learn, and will get the job done.
  • If you want to be cross-platform, or have a more complicated game: Use Unity — it’s more powerful and flexible.

If you think Unity is for you, check out our book Unity Games by Tutorials.

Otherwise, keep reading on to get started with this SpriteKit tutorial!

Getting Started

Start by downloading the project files at the top or bottom of this page. Open the starter project and navigate to GameViewController.swift. Add the following at the end of viewDidLoad():

let scene = GameScene(size: view.bounds.size)
let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
scene.scaleMode = .resizeFill
skView.presentScene(scene)

GameViewController is a normal UIViewController, except that its root view is an SKView, which is a view that contains a SpriteKit scene.

Here, you’ve instructed viewDidLoad() to create a new instance of the GameScene on startup, with the same size as the view itself. Setting skView.showsFPS to true means a frame rate indicator will be displayed.

That’s it for the initial setup; now it’s time to get something on the screen!

Adding a Sprite

Open GameScene.swift and add with the following to GameScene:

// 1
let player = SKSpriteNode(imageNamed: "player")
  
override func didMove(to view: SKView) {
  // 2
  backgroundColor = SKColor.white
  // 3
  player.position = CGPoint(x: size.width * 0.1, y: size.height * 0.5)
  // 4
  addChild(player)
}

Here’s what this does, step by step:

  1. Here you declare a private constant for the player (i.e. the ninja), which is an example of a sprite. As you can see, creating a sprite is easy — simply pass in the name of the image to use.
  2. Setting the background color of a scene in SpriteKit is as simple as setting the backgroundColor property. Here you set it to white.
  3. You position the sprite to be 10% across horizontally, and centered vertically.
  4. To make the sprite appear on the scene, you must add it as a child of the scene.

Build and run, and voilà — ladies and gentlemen, the ninja has entered the building!

SpriteKit Tutorial

Moving Monsters

Next you want to add some monsters into your scene for your ninja to combat. To make things more interesting, you want the monsters to be moving — otherwise there wouldn’t be much of a challenge! You’ll create the monsters slightly off screen to the right and set up an action that tells them to move to the left.

Add the following methods to GameScene.swift, inside the class, before the closing curly brace:

func random() -> CGFloat {
  return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}

func random(min: CGFloat, max: CGFloat) -> CGFloat {
  return random() * (max - min) + min
}

func addMonster() {
  
  // Create sprite
  let monster = SKSpriteNode(imageNamed: "monster")
  
  // Determine where to spawn the monster along the Y axis
  let actualY = random(min: monster.size.height/2, max: size.height - monster.size.height/2)
  
  // Position the monster slightly off-screen along the right edge,
  // and along a random position along the Y axis as calculated above
  monster.position = CGPoint(x: size.width + monster.size.width/2, y: actualY)
  
  // Add the monster to the scene
  addChild(monster)
  
  // Determine speed of the monster
  let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
  
  // Create the actions
  let actionMove = SKAction.move(to: CGPoint(x: -monster.size.width/2, y: actualY),
                                 duration: TimeInterval(actualDuration))
  let actionMoveDone = SKAction.removeFromParent()
  monster.run(SKAction.sequence([actionMove, actionMoveDone]))
}

The first part of addMonster() should make sense based on what you’ve learned so far: you do some simple calculations to determine where you want to create the object, set the position of the object, and add it to the scene the same way you did for the player sprite.

The new element here is adding actions. SpriteKit provides a lot of extremely useful built-in actions that help you easily change the state of sprites over time, such as move actions, rotate actions, fade actions, animation actions, and more. Here you use three actions on the monster:

  • SKAction.move(to:duration:): You use this action to make the object move off-screen to the left. You can specify how long the movement should take, and here you vary the duration randomly from 2-4 seconds.
  • SKAction.removeFromParent(): SpriteKit comes with a helpful action that removes a node from its parent, effectively deleting it from the scene. Here you use this action to remove the monster from the scene when it is no longer visible. This is important because otherwise you would have an endless supply of monsters and would eventually consume all device resources.
  • SKAction.sequence(_:): The sequence action allows you to chain together a sequence of actions that are performed in order, one at a time. This way, you can have the “move to” action performed first, and once it is complete, you perform the “remove from parent” action.
Note: This code block includes some helper methods to generate a random number within a range using arc4random(). This suffices for the simple random number generation needs in this game, but if you want more advanced functionality, check out the random number APIs in GameplayKit.

One last thing before you move on. You need to actually call the method to create monsters! And to make things fun, let’s have monsters continuously spawning over time.

Simply add the following code to the end of didMove(to:):

run(SKAction.repeatForever(
      SKAction.sequence([
        SKAction.run(addMonster),
        SKAction.wait(forDuration: 1.0)
        ])
    ))

Here you run a sequence of actions to call a block of code, and then wait for 1 second. You repeat this sequence of actions endlessly.

That’s it! Build and run the project; now you should see monsters happily moving across the screen:

SpriteKit Tutorial

Shooting Projectiles

At this point, the ninja is just begging for some action — so it’s time to add shooting! There are many ways you could implement shooting, but for this game you’re going to make it so that when the user taps the screen, a projectile is shot from the player in the direction of the tap.

You’ll want to use a “move to” action to implement this, but in order to use this you have to do a little math. The “move to” action requires you to give a destination for the projectile, but you can’t just use the touch point because the touch point represents the direction to shoot relative to the player. You actually want to keep the projectile moving through the touch point until it goes off-screen.

Here’s a picture that illustrates the matter:

Projectile Triangle

As you can see, you have a small triangle created by the x and y offset from the origin point to the touch point. You just need to make a big triangle with the same ratio — and you know you want one of the endpoints to be off the screen.

To run these calculations, it really helps if you have some basic vector math routines you can call (like methods to add and subtract vectors). However, SpriteKit doesn’t have any by default so you’ll have to write your own.

Luckily they are very easy to write thanks to the power of Swift operator overloading. Add these functions to the top of your file, right before the GameScene class:

func +(left: CGPoint, right: CGPoint) -> CGPoint {
  return CGPoint(x: left.x + right.x, y: left.y + right.y)
}

func -(left: CGPoint, right: CGPoint) -> CGPoint {
  return CGPoint(x: left.x - right.x, y: left.y - right.y)
}

func *(point: CGPoint, scalar: CGFloat) -> CGPoint {
  return CGPoint(x: point.x * scalar, y: point.y * scalar)
}

func /(point: CGPoint, scalar: CGFloat) -> CGPoint {
  return CGPoint(x: point.x / scalar, y: point.y / scalar)
}

#if !(arch(x86_64) || arch(arm64))
  func sqrt(a: CGFloat) -> CGFloat {
    return CGFloat(sqrtf(Float(a)))
  }
#endif

extension CGPoint {
  func length() -> CGFloat {
    return sqrt(x*x + y*y)
  }
  
  func normalized() -> CGPoint {
    return self / length()
  }
}

These are standard implementations of some vector math functions. If you’re confused about what’s going on here or are new to vector math, check out this quick vector math explanation.

Next, add a new method to the GameScene class — again, before the closing curly brace:

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
  // 1 - Choose one of the touches to work with
  guard let touch = touches.first else {
    return
  }
  let touchLocation = touch.location(in: self)
  
  // 2 - Set up initial location of projectile
  let projectile = SKSpriteNode(imageNamed: "projectile")
  projectile.position = player.position
  
  // 3 - Determine offset of location to projectile
  let offset = touchLocation - projectile.position
  
  // 4 - Bail out if you are shooting down or backwards
  if offset.x < 0 { return }
  
  // 5 - OK to add now - you've double checked position
  addChild(projectile)
  
  // 6 - Get the direction of where to shoot
  let direction = offset.normalized()
  
  // 7 - Make it shoot far enough to be guaranteed off screen
  let shootAmount = direction * 1000
  
  // 8 - Add the shoot amount to the current position
  let realDest = shootAmount + projectile.position
  
  // 9 - Create the actions
  let actionMove = SKAction.move(to: realDest, duration: 2.0)
  let actionMoveDone = SKAction.removeFromParent()
  projectile.run(SKAction.sequence([actionMove, actionMoveDone]))
}

There's a lot going on here, so here's what it does, step by step.

  1. One of the cool things about SpriteKit is that it includes a category on UITouch with location(in:) and previousLocation(in:) methods. These let you find the coordinate of a touch within an SKNode's coordinate system. In this case, you use it to find out where the touch is within the scene's coordinate system.
  2. You then create a projectile and place it where the player is to start. Note you don't add it to the scene yet, because you have to do some sanity checking first - this game does not allow the ninja to shoot backwards.
  3. You then subtract the projectile's current position from the touch location to get a vector from the current position to the touch location.
  4. If the X value is less than 0, this means the player is trying to shoot backwards. This is not allowed in this game (real ninjas don't look back!), so just return.
  5. Otherwise, it's OK to add the projectile to the scene.
  6. Convert the offset into a unit vector (of length 1) by calling normalized(). This will make it easy to make a vector with a fixed length in the same direction, because 1 * length = length.
  7. Multiply the unit vector in the direction you want to shoot in by 1000. Why 1000? It will definitely be long enough to go past the edge of the screen. :]
  8. Add the shoot amount to the current position to get where it should end up on the screen.
  9. Finally, create move(to:,duration:) and removeFromParent() actions like you did earlier for the monster.

Build and run. Now your ninja should be able to fire away at the oncoming hordes!

SpriteKit Tutorial

Collision Detection and Physics: Overview

So now you have shurikens flying everywhere, but what your ninja really wants to do is to lay some smack down. So, it's time to add some code to detect when your projectiles intersect your targets.

One of the nice things about SpriteKit is it comes with a physics engine built right in! Not only are physics engines great for simulating realistic movement, but they're also great for collision detection purposes.

You'll set up the game to use SpriteKit's physics engine to determine when monsters and projectiles collide. At a high level, here's what you're going to do:

  • Set up the physics world. A physics world is the simulation space for running physics calculations. One is set up on the scene by default, and you might want to configure a few properties on it, like gravity.
  • Create physics bodies for each sprite. In SpriteKit, you can associate a shape with each sprite for collision detection purposes, and set certain properties on it. This is called a physics body. Note that the physics body does not have to be the exact same shape as the sprite. Usually it's a simpler, approximate shape rather than pixel-perfect, since that's good enough for most games and performance.
  • Set a category for each type of sprite. One of the properties you can set on a physics body is a category, which is a bitmask indicating the group or groups it belongs to. In this game, you're going to have two categories: one for projectiles and one for monsters. Then later when two physics bodies collide, you can easily tell what kind of sprite you're dealing with by looking at its category.
  • Set a contact delegate. Remember that physics world from earlier? Well, you can set a contact delegate on it to be notified when two physics bodies collide. There you'll write some code to examine the categories of the objects, and if they're the monster and projectile, you'll make them go boom!

Now that you understand the battle plan, it's time to put it into action!

Collision Detection and Physics: Implementation

Start by adding this struct to the top of GameScene.swift:

struct PhysicsCategory {
  static let none      : UInt32 = 0
  static let all       : UInt32 = UInt32.max
  static let monster   : UInt32 = 0b1       // 1
  static let projectile: UInt32 = 0b10      // 2
}

This code sets up the constants for the physics categories you'll need in a bit — no pun intended! :]

Note: You may be wondering what the fancy syntax is here. The category on SpriteKit is just a single 32-bit integer, acting as a bitmask. This is a fancy way of saying each of the 32-bits in the integer represents a single category (and hence you can have 32 categories max). Here you're setting the first bit to indicate a monster, the next bit over to represent a projectile, and so on.

Next, create an extension at the end of GameScene.swift implementing the SKPhysicsContactDelegate protocol:

extension GameScene: SKPhysicsContactDelegate {

}

Then inside didMove(to:) add these lines after adding the player to the scene:

physicsWorld.gravity =.zero
physicsWorld.contactDelegate = self

This sets up the physics world to have no gravity, and sets the scene as the delegate to be notified when two physics bodies collide.

Inside addMonster(), add these lines right after creating the monster sprite:

monster.physicsBody = SKPhysicsBody(rectangleOf: monster.size) // 1
monster.physicsBody?.isDynamic = true // 2
monster.physicsBody?.categoryBitMask = PhysicsCategory.monster // 3
monster.physicsBody?.contactTestBitMask = PhysicsCategory.projectile // 4
monster.physicsBody?.collisionBitMask = PhysicsCategory.none // 5

Here's what this does:

  1. Create a physics body for the sprite. In this case, the body is defined as a rectangle of the same size as the sprite, since that's a decent approximation for the monster.
  2. Set the sprite to be dynamic. This means that the physics engine will not control the movement of the monster. You will through the code you've already written, using move actions.
  3. Set the category bit mask to be the monsterCategory you defined earlier.
  4. contactTestBitMask indicates what categories of objects this object should notify the contact listener when they intersect. You choose projectiles here.
  5. collisionBitMask indicates what categories of objects this object that the physics engine handle contact responses to (i.e. bounce off of). You don't want the monster and projectile to bounce off each other — it's OK for them to go right through each other in this game — so you set this to .none.

Next add some similar code to touchesEnded(_:with:), right after the line setting the projectile's position:

projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
projectile.physicsBody?.isDynamic = true
projectile.physicsBody?.categoryBitMask = PhysicsCategory.projectile
projectile.physicsBody?.contactTestBitMask = PhysicsCategory.monster
projectile.physicsBody?.collisionBitMask = PhysicsCategory.none
projectile.physicsBody?.usesPreciseCollisionDetection = true

As a test, see if you can understand each line here and what it does. If not, just refer back to the points explained above!

As a second test, see if you can spot two differences. Answer below!

Solution Inside: What Are the Differences? SelectShow>

Next, add a method that will be called when the projectile collides with the monster before the closing curly brace of GameScene. Nothing calls this automatically; you will be calling this later.

func projectileDidCollideWithMonster(projectile: SKSpriteNode, monster: SKSpriteNode) {
  print("Hit")
  projectile.removeFromParent()
  monster.removeFromParent()
}

All you do here is remove the projectile and monster from the scene when they collide. Pretty simple, eh?

Now it's time to implement the contact delegate method. Add the following new method to the extension you made earlier:

func didBegin(_ contact: SKPhysicsContact) {
  // 1
  var firstBody: SKPhysicsBody
  var secondBody: SKPhysicsBody
  if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
    firstBody = contact.bodyA
    secondBody = contact.bodyB
  } else {
    firstBody = contact.bodyB
    secondBody = contact.bodyA
  }
 
  // 2
  if ((firstBody.categoryBitMask & PhysicsCategory.monster != 0) &&
      (secondBody.categoryBitMask & PhysicsCategory.projectile != 0)) {
    if let monster = firstBody.node as? SKSpriteNode,
      let projectile = secondBody.node as? SKSpriteNode {
      projectileDidCollideWithMonster(projectile: projectile, monster: monster)
    }
  }
}

Since you set the scene as the physics world's contactDelegate earlier, this method will be called whenever two physics bodies collide and their contactTestBitMasks are set appropriately.

There are two parts to this method:

  1. This method passes you the two bodies that collide, but does not guarantee that they are passed in any particular order. So this bit of code just arranges them so they are sorted by their category bit masks so you can make some assumptions later.
  2. Here is the check to see if the two bodies that collided are the projectile and monster, and if so, the method you wrote earlier is called.

Build and run, and now when your projectiles intersect targets they should disappear!

SpriteKit Tutorial

Finishing Touches

You’re pretty close to having an extremely simple but workable game now. You just need to add some sound effects and music — what kind of game doesn’t have sound? — and some simple game logic.

The resources in the project for this tutorial already have some cool background music and an awesome pew-pew sound effect. You just need to play them!

To do this, add these line to the end of didMove(to:):

let backgroundMusic = SKAudioNode(fileNamed: "background-music-aac.caf")
backgroundMusic.autoplayLooped = true
addChild(backgroundMusic)

This uses SKAudioNode to play and loop the background music for your game.

As for the sound effect, add this line after the guard statement in touchesEnded(_:withEvent:):

run(SKAction.playSoundFileNamed("pew-pew-lei.caf", waitForCompletion: false))

Pretty handy, eh? You can play a sound effect with one line!

Build and run, and enjoy your groovy tunes!

Note: If you don't hear the background music, try running on a device instead of the simulator.

Game Over, Man!

Now, create a new scene that will serve as your “You Win” or “You Lose” indicator. Create a new file with the iOS\Source\Swift File template, name the file GameOverScene and click Create.

Add the following to GameOverScene.swift:

import SpriteKit

class GameOverScene: SKScene {
  init(size: CGSize, won:Bool) {
    super.init(size: size)
    
    // 1
    backgroundColor = SKColor.white
    
    // 2
    let message = won ? "You Won!" : "You Lose :["
    
    // 3
    let label = SKLabelNode(fontNamed: "Chalkduster")
    label.text = message
    label.fontSize = 40
    label.fontColor = SKColor.black
    label.position = CGPoint(x: size.width/2, y: size.height/2)
    addChild(label)
    
    // 4
    run(SKAction.sequence([
      SKAction.wait(forDuration: 3.0),
      SKAction.run() { [weak self] in
        // 5
        guard let `self` = self else { return }
        let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
        let scene = GameScene(size: size)
        self.view?.presentScene(scene, transition:reveal)
      }
      ]))
   }
  
  // 6
  required init(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

There are six parts to point out here:

  1. Set the background color to white, same as you did for the main scene.
  2. Based on the won parameter, the message is set to either "You Won" or "You Lose".
  3. This is how you display a label of text on the screen with SpriteKit. As you can see, it's pretty easy. You just choose your font and set a few parameters.
  4. Finally, this sets up and runs a sequence of two actions. First it waits for 3 seconds, then it uses the run() action to run some arbitrary code.
  5. This is how you transition to a new scene in SpriteKit. You can pick from a variety of different animated transitions for how you want the scenes to display. Here you've chosen a flip transition that takes 0.5 seconds. Then you create the scene you want to display, and use presentScene(_:transition:) on self.view.
  6. If you override an initializer on a scene, you must implement the required init(coder:) initializer as well. However this initializer will never be called, so you just add a dummy implementation with a fatalError(_:) for now.

So far so good! Now you just need to set up your main scene to load the game over scene when appropriate.

Switch back to GameScene.swift, and inside addMonster(), replace monster.run(SKAction.sequence([actionMove, actionMoveDone])) with the following:

let loseAction = SKAction.run() { [weak self] in
  guard let `self` = self else { return }
  let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
  let gameOverScene = GameOverScene(size: self.size, won: false)
  self.view?.presentScene(gameOverScene, transition: reveal)
}
monster.run(SKAction.sequence([actionMove, loseAction, actionMoveDone]))

This creates a new "lose action" that displays the game over scene when a monster goes off-screen. See if you understand each line here, if not refer to the explanation for the previous code block.

Now you should handle the win case too; don't be cruel to your players! :] Add a new property to the top of GameScene, right after the declaration of player:

var monstersDestroyed = 0

And add this to the bottom of projectileDidCollideWithMonster(projectile:monster:):

monstersDestroyed += 1
if monstersDestroyed > 30 {
  let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
  let gameOverScene = GameOverScene(size: self.size, won: true)
  view?.presentScene(gameOverScene, transition: reveal)
}

Here you keep track of how many monsters the player destroys. If the player successfully destroys more than 30 monsters, the game ends and the player wins the game!

Build and run. You should now have win and lose conditions and see a game over scene when appropriate!

SpriteKit Tutorial

Where to Go From Here?

And that's a wrap! You can download the project files at the top or bottom of this page.

I hope you enjoyed learning about SpriteKit and are inspired to make your own game. If you have any questions or comments about this SpriteKit tutorial, please join the discussion below!

If you want to learn more about SpriteKit, you should check out our book: 2D Apple Games by Tutorials:

In this book we teach you everything you need to know to make great games for iOS, tvOS, watchOS and macOS: from physics, to tile maps, to particle systems, and even how to make your games "juicy" with polish and special effects!

The post SpriteKit Tutorial for Beginners appeared first on Ray Wenderlich.

Video Tutorial: Intermediate Debugging Part 1: Breaking in UIKit

Data Persistence With Room

$
0
0

Data Persistence With Room

Many apps need to deal with persisting data. Perhaps you have an app that stores your favorite pet photos, a social networking app for cat lovers, or an app to maintain lists of things that you might need for your next vacation.

Android provides a number of options, including:

  • Shared Preferences: For storing primitive data in key-value pairs.
  • Internal Storage: For storing private data on device storage.
  • External Storage: For storing public data on shared external storage.
  • SQLite Databases: For storing structured data in a private database.

When your data is structured, and you need to be able to do things such as searching for records in that data, a SQLite database is often the best choice. This is where Room comes in. Room is a SQLite wrapper library from Google that removes much of the boilerplate code you need to interact with SQLite, and also adds compile-time checking of your SQL queries.

In this tutorial, you will build an application that creates a generic list that could be used as a shopping, to-do or packing list. In this tutorial you will learn:

  • The basics of setting up a Room database.
  • How to use a DAO to Create and Read data.
  • The basics of unit testing your persistence layer.
  • How to hook up your database to an Android UI.

Note: This tutorial assumes that you have some experience developing Android applications. A few points to keep in mind:

  • You will be using the Android RecyclerView to display lists. If you’ve never used them or need a refresher, the Android RecyclerView Tutorial with Kotlin is a great place to start.
  • This tutorial utilizes Data Binding and Binding Adapters. Again, if you have never used these or need a refresher, you should take a look at the data binding documentation from the Android project pages.
  • The code snippets in this tutorial do not include the needed import statements. Use the key combination Option-Return on Mac Alt-Enter on PC to resolve any missing dependencies as you work through your project.
  • Introduction to Android Data Persistence

    Classes, Tables, Rows and Instances

    To understand Room, it is helpful to understand the sum of its parts, so let’s start with a simple example of storing the names, addresses and phone numbers of a few people.

    When you are developing applications using an object-oriented programming language like Kotlin, you use classes to represent the data that you are storing. In our example you could create a class called Person, with the following attributes:

    • name
    • address
    • phoneNumber

    For each person you’d then create an instance of a Person, with unique data for that individual.

    With a SQL relational database, you would model the Person class as a table. Each instance of that person would be a row in that table. In order to store and retrieve this data, SQL commands need to be be issued to the database, telling it to retrieve and store the data.

    For example, to store a record in a table you might use a command like:

    INSERT INTO Persons (Name, Address, TelephoneNumber)
    VALUES ('Grumpy Cat', '1 Tuna Way, Los Angeles CA', '310-867-5309');
    

    In the early days of Android, if you had a Person object that you wanted to store in the SQLite database, you would have had to create glue code that would turn objects into to SQL and SQL into objects.

    Glue code

    ORMs and Android

    Long before the days of Android, developers in other object-oriented languages started using a class of tool called an ORM to solve this problem. ORM stands for Object Relational Mapper. The best way to think of it is as a tool designed to automatically generate glue code to map between your object instances and rows in your database.

    When Android came on the scene, no ORM existed for the Android environment. Over the years, open-source ORM frameworks emerged, including DBFlow, GreenDAO, OrmLite, SugarORM and Active Android. While these solutions have helped to solve the basic problem of reducing glue code, the developer community has never really gravitated towards one (or two) common solutions. This has led to significant fragmentation and limitations in many of these frameworks, especially with more complex application lifecycles.

    Google’s Android Architecture Components and Room

    Beyond data persistence, a number of patterns have emerged within the Android development community to deal with things such as maintaining state during application lifecycle changes, callbacks, separating application concerns, view models for MVVM applications, etc. Luckily, in 2017, Google took some of the best practices from the community and created a framework called the Android Architecture Components. Included in this framework was a new ORM called Room. With Room you have an ORM to generate your glue code with the backing of the creators of Android.

    Room as Glue

    Getting Started With Room

    To kick things off, start by downloading the materials for this tutorial (you can find the link at the top or bottom of this tutorial), unzip it, and start Android Studio 3.1.1 or later.

    In the Welcome to Android Studio dialog, select Import project (Eclipse ADT, Gradle, etc.).

    Welcome to Android Studio

    Choose the ListMaster directory of the starter project, and click Open.

    Import project

    If you see a message to update the project’s Gradle plugin since you’re using a later version of Android Studio, go ahead and choose “Update”.

    Check out the project for the List Master app and you will see two packages for list categories and list items. You’ll be working only in the first package in this tutorial.

    Build and run the application and your app will look like this:

    Starter app

    Under the Gradle Scripts part of your project you’ll see a build.gradle file with a (Module:app) notation. Double-click to open it and add the following dependencies that add Room to your project.

    implementation "android.arch.persistence.room:runtime:1.0.0"
    kapt "android.arch.persistence.room:compiler:1.0.0"
    annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
    androidTestImplementation "android.arch.persistence.room:testing:1.0.0"
    androidTestImplementation "android.arch.core:core-testing:1.1.0"
    

    Go ahead and sync Gradle files once you’ve made the change.

    You now have your Room dependencies. Next, you will need to add the following things to use Room in your app:

    • Entity: An entity represents the data model that you are mapping to a table in your database.
    • DAO: short for Data Access Object, an object with methods used to access the database.
    • Database: A database holder that serves as the main access point for the connection to your database.

    Entities

    An entity is an object that holds data for you to use in your application with some extra information to tell Room about its structure in the database. To start, you are going to create an Entity to store a category_name and id in a table called list_categories.

    Under the com.raywenderlich.listmaster.listcategory package in your app you will see a Kotlin class named ListCategory. Open it by double-clicking and replace the data class with the following code:

    @Entity(tableName = "list_categories")
    data class ListCategory(@ColumnInfo(name="category_name") var categoryName: String,
                            @ColumnInfo(name="id") @PrimaryKey(autoGenerate = true) var id: Long = 0)
    

    The @Entity(tableName = "list_categories") annotation is telling Room that this is an Entity object that is mapped to a table called list_categories. The @ColumnInfo annotation is telling Room about the columns in your table. For example, the name argument in the @ColumnName(name="category_name") annotation tells Room that the data class property categoryName directly after it has a column name of category_name in the table.

    DAOs

    Now that you have your Entity which contains your data, you are going to need a way to interact it. This is done with a data access object, also referred to as a DAO. To start out, you are going to create a DAO to insert and retrieve records from the table that you’ve created with your Entity.

    To do that, right-click the listcategory package in com.reywenderlich.listmaster in your project, select New > Kotlin File/Class, give it a name of ListCategoryDao and press OK. When the editor opens up your new file, paste the following code snippet:

    @Dao
    interface ListCategoryDao {
    
      @Query("SELECT * FROM list_categories")
      fun getAll(): List<ListCategory>
    
      @Insert
      fun insertAll(vararg listCategories: ListCategory)
    }
    

    The first thing you will notice is that for a DAO you’re creating an interface instead of a class. That is because Room is creating the implementation for you. The @Dao annotation tells Room that this is a Dao interface.

    The @Query annotation on getAll() tells room that this function definition represents a query and takes a parameter in the form of a SQL statement. In this case you have a statement that retrieves all of the records in the list_categories table.

    Your getAll() method has a return type of List. Under the hood, Room looks at the results of the query and maps any fields that it can to the return type you have specified. In your case, you’re querying for multiple ListCategory entities and have your return value specified as a List of ListCategory items.

    If you have a query that returns data that Room is not able to map, Room will print a CURSOR_MISMATCH warning and will continue to set any fields that it’s able to.

    Your insertAll method is annotated with @Insert. This tells Room that you are inserting data into the database. Because Room knows about the table and its columns, it takes care of generating the SQL for you.

    Database

    Now that you have an Entity and a DAO, you’re going to need an object to tie things together. This is what the Database object does. Under the com.reywenderlich.listmaster package in your app, create a new Kotlin File/Class called AppDatabase and paste in the following code:

    @Database(entities = [(ListCategory::class)], version = 1)
    abstract class AppDatabase : RoomDatabase() {
      abstract fun listCategoryDao(): ListCategoryDao
    }
    

    You tell Room that the class is a Database object using the @Database annotation. The entities parameter tells your database which entities are associated with that database. There is also a version that is set to 1. The database version will need to be changed when performing a database migration, which will be covered in a later tutorial.

    Your database class can be named whatever you want it to be, but it needs to be abstract, and it needs to extend RoomDatabase. All of your DAOs need to have abstract methods that return the corresponding DAO. This is how Room associates a DAO with a database.

    Database Instances

    In order to use your database, you’re going to need to create an instance of it in your application. Although you only needed to create a few classes, behind the scenes, Room is doing a lot of work for you to manage, map and generate SQL. Because of this, an instance of Room is resource-intensive, which is why its creators recommend that you use a singleton pattern when when using it in your application.

    An easy way to achieve that is by creating an instance of the database when the application is loaded, and then referencing it at the Application level. Go to the com.reywenderlich.listmaster package, open ListMasterApplication by double clicking, and replace the class with the following code:

    class ListMasterApplication: Application() {
    
      companion object {
        var database: AppDatabase? = null
      }
    
      override fun onCreate() {
        super.onCreate()
        ListMasterApplication.database = Room.databaseBuilder(this, AppDatabase::class.java,
            "list-master-db").build()
      }
    }
    

    When looking at the call to create your database instance you’ll notice that three parameters are passed in. A reference to the app context, a reference to your database class, and a name. Behind the scenes, this name corresponds to a filename that is used to store your database in your apps’ internal storage.

    Testing the Essence of your Database with Espresso

    When you think about espresso, your first thought may be about about a machine creating a delicious brown nectar that gives you energy during the day. :]

    Espresso

    While the happiness and energy from a good cup of espresso are always helpful when you’re developing Android applications, in this case, we are talking about an Android testing framework called Espresso.

    Now, to test your database, you could write some code in your activity that inserts data and then prints out the results. You could also begin to wire it to a UI. But, this can be treated as a separate testable piece of the application to make sure your database is set up correctly.

    If you haven’t worked with Espresso or unit testing before, don’t worry, we’ll guide you through what you need test your database with it. So grab your cup of espresso and let’s get testing!

    In your project, there’s a com.raywenderlich.listmaster package with (androidTest) next to it.

    androidTest package

    Under that package, there’s a Kotlin file called ListCategoryDaoTest. Open it by double-clicking and replace the contents with the following code:

    @RunWith(AndroidJUnit4::class)
    class ListCategoryDaoTest {
    
      @Rule
      @JvmField
      val rule: TestRule = InstantTaskExecutorRule()
    
      private lateinit var database: AppDatabase
      private lateinit var listCategoryDao: ListCategoryDao
    
      @Before
      fun setup() {
        val context: Context = InstrumentationRegistry.getTargetContext()
        try {
          database = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
              .allowMainThreadQueries().build()
        } catch (e: Exception) {
          Log.i("test", e.message)
        }
        listCategoryDao = database.listCategoryDao()
      }
      
      @After
      fun tearDown() {
        database.close()
      }
    }
    

    So, what is going on here? You created database and DAO properties for the test class. In your setup() method, you are creating an in-memory version of our database using Room.inMemoryDatabaseBuilder and getting a reference to your listCategoryDao. You close the database in tearDown().

    Now, using the magic of testing you’re going to create some queries and use asserts to see what is going on. Add the following test into the test class:

    @Test
    fun testAddingAndRetrievingData() {
      // 1
      val preInsertRetrievedCategories = listCategoryDao.getAll()
    
      // 2
      val listCategory = ListCategory("Cats", 1)
      listCategoryDao.insertAll(listCategory)
    
      //3
      val postInsertRetrievedCategories = listCategoryDao.getAll()
      val sizeDifference = postInsertRetrievedCategories.size - preInsertRetrievedCategories.size
      Assert.assertEquals(1, sizeDifference)
      val retrievedCategory = postInsertRetrievedCategories.last()
      Assert.assertEquals("Cats", retrievedCategory.categoryName)
    }
    

    Your test is doing the following:

    1. First, your test calls the getAll() method on your DAO that will get all of the current records in your database.
    2. Second, you create an entity object and insert it into your database.
    3. Finally, you perform some Assert.assertEquals calls on the result set after the record is added to ensure that a record was added. To do that, you are comparing the size before and after adding a record to make sure that the difference is 1, and then you look at the last record to ensure that its elements match the record you added.

    When you run an Espresso test, it installs your application on a device or emulator and then runs all of the code in your tests. Under the com.raywenderlich.listmaster package with the (androidTest) notation, right-click your ListCategoryDaoTest file and select Run ‘ListCategoryDaoTest’. You can also click the green arrow next to the test class name, or click the arrow next to an individual test.

    Run test menu

    When it asks you to select a deployment target, select your favorite emulator or device.

    Select device

    Your project will build, install, and the tests will run. The bottom of your Android Studio window should look like this:

    Passing test

    Wiring Up Your Interface

    While a unit test against your database is a good start, when you try to run the application, you may feel like this…

    Where is the data?

    To get beyond that, you’d need to have an interface to interact with your database. Your sample project already has the skeleton elements for your applications layout, colors etc. You’re going to hook up some queries to read from and write to your database.

    To make that process smoother, you’ll use data binding to feed that data into and out of the interface. Your list will also be using a Recycler View.

    When you’re finished, you’ll have an app that shows a list of categories. Each row will have a circle that has the first letter of the category on the left, with one of six colors based on a hash code of the letter.

    To begin, go to the listcategory package under com.raywenderlich.listmaster, and double-click the file called ListCategoryViewModel. You will see the following code:

    data class ListCategoryViewModel(val listCategory: ListCategory = ListCategory("")) {
    
      private val highlightColors = arrayOf(R.color.colorPrimary, R.color.colorPrimaryDark, R.color.colorAccent,
          R.color.primaryLightColor, R.color.secondaryLightColor, R.color.secondaryDarkColor)
    
      fun getHighlightLetter(): String {
        return listCategory.categoryName.first().toString()
      }
    
      fun getHighlightLetterColor(): Int {
        val uniqueIdMultiplier = getHighlightLetter().hashCode().div(6)
        val colorArrayIndex = getHighlightLetter().hashCode() - (uniqueIdMultiplier * 6)
        Log.i("color", colorArrayIndex.toString())
        return (highlightColors[colorArrayIndex])
      }
    }
    

    In this code, you’re using a standard data class and adding some extra logic for setting up the circle and highlight letter. The highlight tint is hooked in using the adapter in your DataBindingAdapters.kt file. You’re going to display your list in a RecyclerView, which consists of a ViewHolder and Adapter that you will initialize in your Activity.

    To use the adapter, you’ll need to a layout for each item. Your starter project already has one. To take a look at it, open the file under the res/layout part of your project called holder_list_category_item.xml, a snippet of which is shown here:

    
      <data>
    
        <variable
          name="listCategoryItem"
          type="com.raywenderlich.listmaster.listcategory.ListCategoryViewModel" />
      </data>
    
      <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:layout_editor_absoluteX="8dp">
    
        <TextView
          android:id="@+id/category_header"
          android:layout_width="0dp"
          android:layout_height="wrap_content"
          android:layout_marginBottom="16dp"
          android:layout_marginEnd="8dp"
          android:layout_marginStart="16dp"
          android:layout_marginTop="16dp"
          android:background="@drawable/circle"
          android:gravity="center"
          android:highlight_tint="@{listCategoryItem.highlightLetterColor}"
          android:text="@{listCategoryItem.highlightLetter}"
          android:textAlignment="center"
          android:textColor="@android:color/white"
          android:textSize="20sp"
          app:layout_constraintBottom_toBottomOf="parent"
          app:layout_constraintEnd_toStartOf="@+id/category_name"
          app:layout_constraintHorizontal_chainStyle="spread"
          app:layout_constraintHorizontal_weight=".15"
          app:layout_constraintStart_toStartOf="parent"
          app:layout_constraintTop_toTopOf="parent" />
    
    

    The listCategoryItem in the data tag is your bound View Model. The various @{...} items in the text view attribute values make use of the data in the View Model. You will also notice a text view attribute called highlight_tint. This is a custom field that was added via a BindingAdapter in the file named DataBindingAdapters.kt under com.raywenderlich.listmaster in your starter package.

    Your starter project already has a ListCategoryAdapter and ListCategoryViewHolder under the listcategory package.

    listcategory package

    In order to display your list, you’re going to need a RecyclerView for the ListCategoriesActivity. Luckily your starter project has that in the content_list_categories.xml layout, which is included under the res/layout section of your project.

    <?xml version="1.0" encoding="utf-8"?>
    <layout>
    
        <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            tools:context="com.raywenderlich.listmaster.ListCategoriesActivity"
            tools:showIn="@layout/activity_list_categories">
    
            <android.support.v7.widget.RecyclerView
                android:id="@+id/list_category_recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
    
        </android.support.constraint.ConstraintLayout>
    </layout>
    

    Now it’s time to wire up your activity. To do that, you will need to add a few things to ListCategoriesActivity. Go to the listcategory package under com.raywenderlich.listmaster and open ListCategoriesActivity by double-clicking.

    To start, paste in the following properties near the top of the class:

    private lateinit var contentListsBinding: ContentListCategoriesBinding
    private lateinit var appDatabase: AppDatabase
    private lateinit var listCategoryDao: ListCategoryDao
    

    The first property will be generated by the data binding API when the project is built. The other properties are for references to the database and the list category DAO. If necessary, add the following import to your import statments:

    import com.raywenderlich.listmaster.databinding.ContentListCategoriesBinding
    

    Next, add the following function to the bottom of the activity:

    private fun setupRecyclerAdapter() {
      val recyclerViewLinearLayoutManager = LinearLayoutManager(this)
      contentListsBinding = activityListsBinding.contentLists!!
      contentListsBinding.listCategoryRecyclerView.layoutManager = recyclerViewLinearLayoutManager
      listCategoryAdapter = ListCategoryAdapter(listOf(), this)
      contentListsBinding.listCategoryRecyclerView.adapter = listCategoryAdapter
    }
    

    As its name suggests, this function sets up the recycler view. In order to initialize this before you have a list of items from the database, an empty list is passed to its adapter.

    Now that you have these building blocks, paste the snippet below right after the setupAddButton() call in your onCreate() method:

    // Set up our database
    appDatabase = ListMasterApplication.database!!
    listCategoryDao = appDatabase.listCategoryDao()
    
    // Set up our recycler adapter
    setupRecyclerAdapter()
    

    This is getting references to your database and listCategoryDao for use in your activity, along with calling your setupRecyclerAdapter() method.

    Next, add code to query Room for the list of categories and add them to the adapter by pasting the onResume method into your ListCategoriesActivity class:

    override fun onResume() {
      super.onResume()
      AsyncTask.execute({
        listCategoryAdapter.categoryList = listCategoryDao.getAll()
        runOnUiThread { listCategoryAdapter.notifyDataSetChanged() }
      })
    }
    

    You may have noticed that this call the the DAO is run in a AsyncTask. This is because these database calls are reaching out to your file system and could take a while to complete, especially if you have a complex query. A long query can cause the UI to freeze or even produce a dreaded Application Not Responding error. Since both of these things can lead to unhappy users, it’s best to always put these calls in a background thread.

    Now that you have your list query wired in, you’re going to need to have a way to add in category items. To do that, your starter project has a dialog layout in a file called dialog_add_category.xml that you are going to hook up to the Add button. Find the function called setupAddButton in your ListCategoriesActivity and update the call to setPositiveButton on alertDialogBuilder to look as follows:

    alertDialogBuilder.setPositiveButton(android.R.string.ok, { dialog: DialogInterface, 
                                                                      which: Int ->
      AsyncTask.execute({
        listCategoryDao.insertAll(listCategoryViewModel.listCategory)
        listCategoryAdapter.categoryList = listCategoryDao.getAll()
        runOnUiThread { listCategoryAdapter.notifyDataSetChanged() }
      })
    })
    

    You insert the new category from the dialog into the database, and then update your list of categories on the adapter and refresh the adapter on the UI thread.

    Now it’s time to run your application! Go to the Run menu and select the Run option. Note: If you select ListCategoryDaoTest instead, it will run your tests instead of your app.

    Run the app

    Next, select the App option:

    App option

    Then, select a device to run it on:

    Select device

    Your app will run and you’ll be able to add categories to your list.

    Add category

    Go ahead and add a few categories. Then stop the app and build and run again.

    Saved Data

    You see that your categories have been persisted in SQLite thanks to Room! :]

    Where To Go From Here?

    You can download the final project using the link at the top or bottom of this tutorial.

    There’s a lot more to learn and do with Room!

    • Migrations for making changes to an existing data store.
    • Indexes to make your queries faster.
    • Type Converters to make it easier to map database types to your own custom types.

    Stay tuned for future Room tutorials where these will be covered. :]

    If you’re just getting started with data persistence on Android and want some more background, you can check out our Saving Data on Android video course, which covers SharedPreferences, saving to files, SQLite and migrations, and also persisting using Room along with other Android Architecture Components such as ViewModel and LiveData.

    As a challenge, you can also try to:

    • Add the ability to delete a list category.
    • Create a onClick event in each category that allows you to edit the category and save it to your database.

    Feel free to share your feedback, findings or ask any questions in the comments below or in the forums. I hope you enjoyed this tutorial on data persistence with Room!

    Puppy

    The post Data Persistence With Room appeared first on Ray Wenderlich.

    RWDevCon 2018 Post-Mortem

    $
    0
    0

    We recently finished our fourth annual hands-on tutorial conference: RWDevCon 2018.

    During our three days together, we had 3 open bars, 4 workshops, 5 inspiration talks, 18 hands-on tutorials, and 275 attendees from 18 countries across the world!

    The conference was a massive success, with an average 4.59 overall rating, and everyone seemed to love the combination of hands-on tutorials, and this year’s theme of “connection”, resulting in a fun and friendly atmosphere.

    Now that Vicki and I are back home, we thought you might like to see to see some pictures and feedback from the attendees, so that’s what this post is all about.

    Then we’ll get into the post-mortem, with three things that went well, and three things that we can improve upon in the future. Let’s dive in!

    Conference Highlights

    Before the conference began, we had 4 optional pre-conference workshops, covering ARKit, Machine Learning, Practical Instruments, and Swift Collection Protocols.

    The next day, Vicki and I kicked things off with the four main ideas of the conference:

    • hands-on experience
    • team coordination
    • inspiration
    • friendship
    • (+ a secret 5th item only veterans know!)

    Then Tammy Coron gave an inspirational keynote that argued that as humans, we have a deep need for social interaction (introvert or not) – so making the effort to connect on a deep level with others is truly worth it.

    After that we started the main event of the conference: the 18 hands-on tutorials, covering topics such as TDD, Advanced Unidirectional Architecture, the Art of the Chart, Xcode Tips & Tricks, Server Side Swift with Kitura, and more. The key difference is that instead of just watching the instructor talk, you code along with him or her!

    Since this conference is so coding-heavy, it can get intense. So in the afternoons, we took a break for something we call inspiration talks, short 18-minute non-technical talks, designed to give attendees some battle-won advice, or new ideas. We had some excellent talks this year, including Daniel Steinberg’s “Game of Life”, which featured magic tricks, and a slide deck in human form! :]

    The theme of this year’s conference was connection, so we ran a series of events this year called RWConnect designed to help attendees connect with each other and foster a friendly and inclusive atmosphere. For example, we ran RWConnect Open Spaces, a series of small group discussions with fellow attendees on topics like “How much architecture is too much?”, “Mental Fitness”, or “Internet of Things”:

    RWConnect events also included a a Design Lab, a Women’s Lunch, a Hackathon, a Board Game Zone, and much more.

    And let’s not forget the epic RWConnect Trivia Night, where James Dempsey broke everyone into small groups and asked us geeky iOS & Apple Trivia questions, in categories such as “Will it Compile?” or “Emoji-idioms”, or “Who said it: Jobs, Cook, or Ive?”

    At the end of the conference, we revealed our special surprise: a pre-release copy of our upcoming books Swift Data Structures & Algorithms and Design Patterns by Tutorials!

    Note: In case you’re wondering, the two new books will be available on our store next week, along with one last surprise we have up our sleeves. :]

    I was so happy to see everyone learning a lot, and having such a great time. Thank you to the amazing RWDevCon community for helping create such a wonderful weekend!

    What Went Well

    From the evaluation forms and people I spoke to, people seemed to really love the conference – I think we’ve created something truly special. Here are a few comments from attendees:

    “RWDevCon is such a great conference and the things you learn here are priceless. I will be back every year for sure.”

    “This year reconfirmed to me that RWDevCon is by far the most useful conference to strengthen my knowledge in iOS and Swift.”

    “Overall, really great fun conference. The emphasis on practical hands on material is invaluable. You also did an amazing job helping people to connect. Trivia night was awesome. Major Props overall!”

    Here are three things I think went particularly well about the conference.

    1) Hands-On Tutorials

    RWDevCon is the only conference that focuses 100% on hands-on tutorials, and people seemed to love this hands-on approach.

    “I loved the variety of topics and the hands-on step through of the demos.”

    “Packed knowledge, quick and fast. No scratching at the surface!”

    “The tutorial sessions were all fantastic.”

    “I liked the hands-on focus, and the quality and consistency of the talks.”

    If you learn best by doing, this is the conference for you.

    2) Friendly and Inclusive Community

    Another thing I heard time and time again from attendees was how open, inclusive, and friendly everyone was. We make an effort to encourage this, since the best thing about conferences are the amazing people you meet. It was wonderful seeing speakers and attendees making new friends!

    “I’ve heard other conferences be referred to as friendly and welcoming, but RWDevCon is the first one I’ve been to that’s 100% lived up to it. Everyone I’ve met has been open and sincere. Thanks so much!”

    “I love how real everyone is. I felt very welcome and accepted.”

    “The friendliest conference I’ve ever been to.”

    A huge thanks to everyone who helped all the introverts like me – I feel so lucky to be a part of such an amazing community! And don’t forget to keep practicing Ray’s Excellent Hi-Q method :]

    3) Team Coordination

    At RWDevCon, we carefully coordinated every detail as a team: we chose the topics as a team based on an attendee vote, we tech edited each other’s materials, and we went through 2 round of practice for every tutorial.

    We even created a 500+ page conference book!

    “Very organized and clear. Materials were awesome.”

    “I loved all the provided materials!”

    “I really appreciated the attention to detail, and the effort put into all of the talks.”

    Our goal is to make sure every tutorial at the conference is up to the high quality standards that folks know and love from our site – and it takes all of us working together to do it!

    What Could Be Improved

    As with anything, there are always some things you can improve. Here are the top 3 suggestions:

    1) Slow down and/or more time

    Let me tell you – giving live tutorials is tough! Finding the right balance between going into enough depth, while not going too fast for folks, is something we keep working on each year.

    This year, we added more time for each tutorial, and replaced the challenge with another demo, and that seemed to be well received. However, many people still reported some tutorials went a bit too fast to follow along with, and recommended slowing down and adding some more time.

    We have some ideas on how to improve that next year, including having a room host watching students and stopping the speaker if things go too fast, and possibly adding more time for tutorials.

    2) No auto-complete

    Another very common piece of feedback is that we should disable autocomplete when giving our demos, as sometimes autocomplete covers what the instructor is typing, and is a little distracting.

    We’ll look into disabling that next year. As a side benefit, it should make the demos go at a bit slower of a pace! :]

    3) One more day?

    There seems to be quite a bit of interest in an additional day of the conference. I must admit this is tempting, as this would make it easier to have room to add more time for tutorials, and avoid overlapping events (like Open Spaces occurring at the same time as the Hackathon).

    Logistics-wise, we’re still trying to figure out if/how to make this happen. Additional days bring additional costs, and we’re also trying to figure out how we can bring more Android content into the mix, since our site is now about Android development too!

    Where To Go From Here?

    Want to know when the next RWDevCon will be?

    We will email everyone who is on our conference newsletter first before we announce the event publicly. If you’d like to be notified if/when tickets become available, sign up here.

    If you didn’t get to attend this year, don’t worry – we’ll be releasing the RWDevCon 2018 Vault soon! This is a massive vault of tutorial videos from the conference: 24+ hours of video tutorials, plus the 500+ page conference book.

    Thanks again for everyone who helped make the fourth annual RWDevCon a success – speakers, attendees, sponsors, and more!

    The post RWDevCon 2018 Post-Mortem appeared first on Ray Wenderlich.

    Screencast: Accessing data using Webhooks

    Viewing all 4396 articles
    Browse latest View live


    <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>