LogoDart Beginner Tutorials

Playing with Your Pet Functions Asynchronous Operations and Null Safety

We will create functions for pet interactions introduce async/await for simulated time delays and handle potential null values from user input using nullable types.

Welcome to Part 4! We're now going to add some dynamic interactivity to your digital pet simulator. In this part, you'll learn about functions, asynchronous operations using async and await, and how to handle potentially null values using Dart's null safety features. This will make your pet simulator more engaging and robust.

Step 1: Creating Functions – feed and play#

Instead of directly modifying the pet's hunger and happiness levels within the main function, let's create separate functions for feeding and playing. This makes our code more organized and reusable. Add these functions below your main function:

Future<void> feed(int hungerLevel, int happinessLevel) async {
  print('Your pet is eating...');
  await Future.delayed(Duration(seconds: 2)); // Simulate eating time
  print('Your pet finished eating!');
  return;
}

Future<void> play(int hungerLevel, int happinessLevel) async {
  print('You are playing with your pet...');
  await Future.delayed(Duration(seconds: 3)); // Simulate playtime
  print('You finished playing with your pet!');
  return;
}

Notice the use of Future<void>. This indicates that the functions are asynchronous and don't return a value. await Future.delayed() simulates a delay, making the actions feel more realistic. This illustrates Dart’s asynchronous programming capabilities.

Step 2: Calling Functions from main#

Now, let's call these new functions from your main function. Replace the existing hunger and happiness modification code with these calls:

// Inside your main function, after getting the pet's name and hunger/happiness levels:
await feed(petHunger, petHappiness); // Call the feed function
await play(petHunger, petHappiness); // Call the play function

// Update the hunger and happiness levels (You'll need to adjust the values based on feeding/playing):
petHunger -=2;
petHappiness += 3;

The await keyword ensures that the main function waits for the feed and play functions to complete before continuing. These will be significantly improved in later steps.

Step 3: Handling Null Values – Robust Input#

Recall that stdin.readLineSync() can return null. Let's add more robust null handling in a more sophisticated way. Replace your existing petName assignment with this improved version:

String? petName = stdin.readLineSync()?.trim();
petName ??= 'Buddy';  //Null-aware assignment operator provides a default value if petName is null

The ?. operator performs a null-aware method call. If stdin.readLineSync() returns null, the trim() method won't be called, preventing a potential error. The ??= operator provides a default value ('Buddy') if petName is still null after the null-aware call. This method elegantly handles the null case without multiple if statements.

Step 4: Putting it All Together#

Here’s how your main function should look after integrating these changes. Note that at this stage the hunger and happiness levels aren’t dynamically updated. This will be completed in the next part.

import 'dart:io';

Future<void> feed(int hungerLevel, int happinessLevel) async {
  print('Your pet is eating...');
  await Future.delayed(Duration(seconds: 2));
  print('Your pet finished eating!');
}

Future<void> play(int hungerLevel, int happinessLevel) async {
  print('You are playing with your pet...');
  await Future.delayed(Duration(seconds: 3));
  print('You finished playing with your pet!');
}

void main() async {
  stdout.write('What would you like to name your pet? ');
  String? petName = stdin.readLineSync()?.trim();
  petName ??= 'Buddy';
  print('Hello, $petName!');

  int petHunger = 5;
  int petHappiness = 7;

  print('${petName}\'s Hunger: $petHunger');
  print('${petName}\'s Happiness: $petHappiness');

  await feed(petHunger, petHappiness);
  await play(petHunger, petHappiness);


  if (petHunger > 7) {
    print('$petName is very hungry!');
  } else if (petHunger > 3) {
    print('$petName is a little hungry.');
  } else {
    print('$petName is not hungry.');
  }

  if (petHappiness < 3) {
    print('$petName is sad.');
  } else if (petHappiness < 7) {
    print('$petName is okay.');
  } else {
    print('$petName is happy!');
  }
}

Run your code! Now you can interact with your pet through functions and see the simulated delays.

What's Next?#

In the next part, "Advanced Pet Care: Object-Oriented Programming and Class Structure," we will introduce the concept of classes to improve the organization and reusability of our code further, creating a DigitalPet class to encapsulate our pet's data and behaviors. Get ready to take your pet simulation to the next level!