Save the Earth – Complete Eco Friendly Tasks with Friends Using a Twilio SMS and Spring Boot App

May 25, 2023
Written by
Tolulope Ayemobola
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by
Diane Phan
Twilion

header - Save the Earth – Complete Eco Friendly Tasks with Friends Using a Twilio SMS and Spring Boot App

The world is facing a major crisis when it comes to the environment. Climate change is real, and it is happening right now. It is a problem that affects us all, and it is our responsibility to take action to protect our planet for future generations. An "Eco-Challenge" app can be a great way to inspire people to make small but impactful changes in their daily lives to reduce their carbon footprint, protect wildlife, and keep our oceans clean.

In this article, we will discuss how to build an "Eco-Challenge" app using Java Spring Boot that allows users to create a series of tasks to complete over the course of Earth Day. The app will use the Twilio Programmable SMS API to send text messages reminding users to complete their eco-challenges or to share updates on their progress with other users, friends and family.

Prerequisites

To make the most of this tutorial, readers are expected to:

  • Have a sound knowledge of Java and Spring Boot.
  • Have a standard IDE like IntelliJ IDEA.
  • Have a working knowledge of SDKs and integrations.
  • Have Postman installed and understand how it works.

Set up the environment

Before diving into the development of the app, you need to make sure that your environment is set up correctly.

The first step in building our "Eco-Challenge" app is to create a new Spring Boot project. Use the Spring Initializr to set up a new project in Spring Boot. Choose "Java" as the language under Project, followed by "Maven." Choose the most recent and stable version of Spring Boot, which is 3.0.5 at the time this article is written.

For the Project Metadata, rename the application Artifact to a name such as "ecotaskapp". This name is the display title that also serves at the project's entrance point. If it may help you organize better, feel free to include a project description.

Choose "Jar" as the Packaging type since the application will function with Java 17 and the integrated Tomcat server that Spring Boot provides.

Click on the Generate button at the bottom of the page and the browser will download a .zip file containing the boilerplate code for the project. Extract the .zip file and open the project in your IDE. For dependencies, ensure that the following dependencies are present in your pom.xml file. You can copy and paste them into the file.

<dependencies>
    <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
    </dependency>
    <dependency>
            <groupId>com.twilio.sdk</groupId>
            <artifactId>twilio</artifactId>
            <version>7.14.2</version>
    </dependency>

    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
    </dependency>
    <dependency>
            <groupId>jakarta.persistence</groupId>
            <artifactId>jakarta.persistence-api</artifactId>
            <version>3.1.0</version>
    </dependency>
    <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
    </dependency>
    <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
    </dependency>
</dependencies>
  • Spring Web: Spring Web is a part of the Spring Framework that provides support for building web applications. It provides a set of tools and libraries that make it convenient to build web applications by providing features such as handling HTTP requests and responses, working with templates, and managing sessions.
  • Spring JPA: Spring JPA is a part of the Spring Framework that provides support for working with the Java Persistence API (JPA). JPA is a standard API for working with relational databases in Java, and Spring JPA provides a set of tools and libraries that make it convenient to work with JPA.
  • Lombok: Lombok is a Java library that provides annotations to help reduce boilerplate code. It provides annotations that generate code at compile time, such as getters and setters, constructors, and equals and hashCode methods. This can help to make code more concise and easier to read, as well as reducing the potential for errors in manually-written boilerplate code.

For database, this project will use the redis dependency for its database function. Redis is widely known for its caching capability, but seeing that it serves the purpose of a database, it fits perfectly to make use of it. Essentially, if the app needs caching, you could continue working with what you have. Follow this tutorial to set up redis according to your operating system.

Follow the first instruction if your OS is a Debian or an Ubuntu-based Linux OS. Otherwise, follow the RPM Feed instructions to install the redis server. Conversely, if you use Windows or Mac, you can follow instructions for your operating system, on the left pane of that link. Add the following dependencies to the pom.xml file to enable redis.

    <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
    </dependency>

Save the file.

Look at the top-right corner of IntelliJ IDEA and find the little icon with an "M" shape. Click it to load Maven changes.

An icon with an "M" shape with a tooltip "Load Maven Changes"

Create Models and Configurations

In your project, create the model, repository, service and web packages under the com.example.ecotaskapp subfolder.

  • Model - This is where the models or entities will be placed.
  • Repository - To store the connection to the database.
  • Service - To store the logic for the eco-challenge app.
  • Web - To build out the web controller for the app.
  • Config - All configurations for classes and dependencies will be placed here.

Configure the Redis Database

Create a file named RedisConfiguration.java under the config subdirectory. Copy and paste the following code into this file:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableRedisRepositories
public class RedisConfiguration {

    @Bean
    public JedisConnectionFactory connectionFactory() {

        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName("localhost");
        configuration.setPort(6379);

        return new JedisConnectionFactory(configuration);
    }

    @Bean
    public RedisTemplate<String, Object> template() {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory());
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new JdkSerializationRedisSerializer());
        template.setValueSerializer(new JdkSerializationRedisSerializer());
        template.setEnableTransactionSupport(true);
        template.afterPropertiesSet();

        return template;
    }
}

The @Configuration annotation tells Spring that this class contains bean definitions that should be used to configure the application context.

The @EnableRedisRepositories annotation is used to enable Spring Data Redis repositories in the application context.

The JedisConnectionFactory bean defines the connection to the Redis database. It creates a RedisStandaloneConfiguration object that contains the host and port of the Redis server. This object is then used to create the JedisConnectionFactory object.

The RedisTemplate bean is used to interact with Redis. It sets the connection factory created earlier and configures the serializers used to convert Java objects to and from Redis values. The setKeySerializer() and setHashKeySerializer() methods set the serializer used for keys and hash keys respectively. The setValueSerializer() method sets the serializer used for values. In this case, the JdkSerializationRedisSerializer is used which is a standard Java serialization mechanism.

The setEnableTransactionSupport() method is used to enable transaction support for Redis. If a Redis transaction is used in the application, the RedisTemplate will execute all Redis commands inside a transaction. Then, the afterPropertiesSet() method initializes the RedisTemplate object.

Overall, this configuration file sets up the necessary beans for connecting to Redis and interacting with Redis in a Spring Boot application.

Create the User model

Create a file named User.java under the model subdirectory. Copy and paste the following code to the newly-created file:

import lombok.*;
import org.springframework.data.redis.core.RedisHash;

import jakarta .persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.redis.core.RedisHash;

import java.util.List;

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@RedisHash("User")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;

    private String lastName;

    private String phoneNumber;

    @Enumerated
    private List<String> followers;
}

The class has four instance variables:

  • id is a unique identifier for the user and is annotated with @Id to indicate that it is the primary key of the corresponding database table. It is also annotated with @GeneratedValue to indicate that the value of this field will be automatically generated by the database, based on the specified strategy (in this case, "GenerationType.IDENTITY").
  • firstName and lastName are strings that represent the user's first and last name, respectively.
  • phoneNumber is a string that represents the user's phone number.
  • followers is a List of strings that represents the usernames of other users who are following this user.

Build the EcoTask model

Just as you did with the user model, create a file named EcoTask.java under the model subdirectory. Copy and paste the following code to the newly created file:

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.redis.core.RedisHash;

import jakarta.persistence.*;

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@RedisHash("EcoTask")
public class EcoTask {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String taskName;

    private boolean completed;

    private String phoneNumber;

}

The class has four instance variables:

  • id is a unique identifier for the EcoTask class and is annotated with @Id to indicate that it is the primary key of the corresponding database table. It is also annotated with @GeneratedValue to indicate that the value of this field will be automatically generated by the database, based on the specified strategy (in this case, GenerationType.IDENTITY).
  • taskName is a string that represents the name of the EcoTask object.
  • completed is a boolean value that indicates whether or not the EcoTask object has been completed.
  • phoneNumber: is a string that represents the identity of the user, whose task is being created and saved.

Create the task request model

Navigate to the model directory and create a file called EcoTaskRequest.java. With this class, the controller receives inputs from the client of our application - web or mobile interface. Paste the code below into this file:

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class EcoTaskRequest {

        private String taskName;
        private String userPhoneNumber;
}

The taskName here is similar to that in EcoTask.java and is a string that represents the name of the EcoTask object. The userPhoneNumber represents the phone number (or means of identification) of the user who is creating the task.

Create the FollowUserRequest model

In the model directory, create a file named FollowUserRequest.java and paste the code snippet below in this file. The purpose of this class is to receive requests for adding a follower to a user or following a user.

@Getter
@Setter
public class FollowUserRequest {
    private Long userId;
    private String followerPhoneNumber;
}

Repository interfaces

Create two new files under the repository subdirectory: UserRepository.java and EcoTaskRepository.java. Paste the following code snippets below in each of their respective files:

import com.twilio.earthday.earth.pieceone.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

   User findByPhoneNumber(String phoneNumber);

}
import com.twilio.earthday.earth.pieceone.model.EcoTask;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface EcoTaskRepository extends JpaRepository<EcoTask, Long> {

}

By extending JpaRepository, the UserRepository and EcoTaskRepository classes inherit all of its CRUD methods such as save(), findAll(), findById(), delete(), etc. In addition to these methods, these repositories can also define custom methods to retrieve data from the database.

Create Services

In this section, you implement the actual business logic for the user to interact with the app.

Build the UserService Class

This class is responsible for the logic of our application. Inside the service sub directory, create a file called UserService.java and paste the code snippet below in it.

import com.twilio.earthday.earth.pieceone.model.EcoTask;
import com.twilio.earthday.earth.pieceone.model.EcoTaskRequest;
import com.twilio.earthday.earth.pieceone.model.User;
import com.twilio.earthday.earth.pieceone.repository.EcoTaskRepository;
import com.twilio.earthday.earth.pieceone.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@Slf4j
public class UserService {

   @Autowired
   private UserRepository userRepository;

   @Autowired
   private EcoTaskRepository ecoTaskRepository;

   @Autowired
   private TwilioService twilioService;

   public User addUser(User user) {
       return userRepository.save(user);
   }

   public User getUser(Long id) {
       return userRepository.findById(id).orElse(null);
   }

   public EcoTask addEcoTask(EcoTaskRequest ecoTaskRequest) {
       EcoTask ecoTask = new EcoTask();
       User user = userRepository.findByPhoneNumber(ecoTaskRequest.getUserPhoneNumber());
       log.info(String.valueOf(user));
       ecoTask.setTaskName(ecoTaskRequest.getTaskName());
       ecoTask.setCompleted(false);
       ecoTask.setPhoneNumber(ecoTaskRequest.getUserPhoneNumber());
       return ecoTaskRepository.save(ecoTask);
   }

   public EcoTask completeEcoTask(Long taskId) {
       EcoTask ecoTask = ecoTaskRepository.findById(taskId).orElse(null);
       assert ecoTask != null;
       ecoTask.setCompleted(true);
       log.info(String.valueOf(ecoTask.isCompleted()));
       return ecoTaskRepository.save(ecoTask);
   }

   public void sendTaskUpdates(Long taskId) {
       EcoTask ecoTask = ecoTaskRepository.findById(taskId).orElse(null);
       assert ecoTask != null;
       User user = userRepository.findByPhoneNumber(ecoTask.getPhoneNumber());

       List<String> followers = user.getFollowers();
       for (String follower : followers) {
           twilioService.sendMessage(follower, "Your friend has completed a task: " + ecoTask.getTaskName());
       }
   }

   public User followUser(Long userId, String follower) {
       User user = userRepository.findById(userId).orElse(null);
       assert user != null;
       user.getFollowers().add(follower);
       userRepository.save(user);

       return user;
   }

   public void sendNotificationAndReminders(Long userId) {
   User user = userRepository.findById(userId).orElse(null);
   assert user != null;
   String message = "Hello there. Your friend, " + user.getFirstName() + " has completed an " +
           "EcoTask Challenge. Don't forget to do your part today!";

   for (String follower : user.getFollowers()) {
       twilioService.sendMessage("+234" + follower.substring(1), message);

   }
}

   public void sendMilestoneNotification(Long userId, EcoTask ecoTask) {
       User user = userRepository.findById(userId).orElse(null);
       assert user != null;
       twilioService
               .sendMessage(user.getPhoneNumber(), "Congratulations! You have reached a milestone in your journey" +
                       " to become more environmentally conscious: " + ecoTask.getTaskName());
   }
}

The UserService class has three private instances: userRepository, ecoTaskRepository, and twilioService. These objects are annotated with @Autowired, which means that Spring will automatically inject the appropriate implementations of these interfaces into the class.

The addUser() method adds a new user to the database by calling the userRepository.save() method. The getUser() method retrieves a user from the database by calling the userRepository.findById() method.

The addEcoTask() method adds a new eco task to the database and associates it with the specified user. It first retrieves the user from the database using the userRepository.findById() method and then sets the user for the eco task using the ecoTask.setUser() method. Finally, it saves the eco task to the database using the ecoTaskRepository.save() method.

The completeEcoTask() method sets the specified eco task as completed by setting the completed attribute to true. It then saves the updated eco task to the database using the ecoTaskRepository.save() method.

The sendTaskUpdates() method sends a task update message to all followers of the user who created the specified task. It retrieves the eco task from the database using the ecoTaskRepository.findById() method, and then retrieves the user who created the task using the ecoTask.getUser() method. It then retrieves the list of followers for the user using the user.getFollowers() method and sends a message to each follower using the twilioService.sendMessage() method.

By annotating the UserService class with @Service, you are telling Spring that this class is a service component and should be managed by the Spring container. This allows you to inject dependencies into the class using the @Autowired annotation and use the service in your controller or other components in your application.

Integrate the Twilio API

The TwilioService class is responsible for implementing the logic for interacting with the Twilio Programmable SMS API. Always abstract your Twilio credentials from being explicitly displayed in this file or any other file. There are a number of ways to achieve this. However, one popular approach is to declare your environmental variables in the application.properties (or application.yml) file.

Open the resources folder and navigate to the application.properties file and paste the following code snippet into it:

#... Other environmental variables

#Twilio Config
ACCOUNTSID=<YOUR-TWILIO-ACCOUNT-SID>
AUTHTOKEN=<YOUR-ACCOUNT-TOKEN>
FROMNUMBER=<FROM-NUMBER>

This configuration is domiciled in the same file housing your database variables and so on. Inside the service subdirectory, create a file named TwilioService.java and paste the code snippet below into it.

import com.twilio.Twilio;
import com.twilio.rest.api.v2010.account.Message;
import com.twilio.type.PhoneNumber;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class TwilioService {

   @Value("${ACCOUNTSID}")
   private String accountSid;

   @Value("${AUTHTOKEN}")
   private String authToken;

   @Value("${FROMNUMBER}")
   private String fromNumber;

   public void sendMessage(String toNumber, String messageBody) {
       Twilio.init(accountSid, authToken);
       Message message = Message.creator(new PhoneNumber(toNumber),
               new PhoneNumber(fromNumber), messageBody).create();
       log.info("Message SID: " + message.getSid());
   }
}

This is an implementation of the TwilioService class that sends SMS messages using the Twilio API. The class has three private instance variables: accountSid, authToken, and fromNumber. These variables are used to store the Twilio ACCOUNT SID, authentication token, and the phone number that will be used as the sender for the SMS messages.

The sendMessage() method takes two parameters: toNumber, which is the phone number of the recipient, and messageBody, which is the text message that will be sent to the recipient.

The Twilio.init() method initializes the Twilio API client with the ACCOUNT SID and authentication token. The Message.creator() method creates a new SMS message and specifies the recipient phone number, sender phone number, and message body. The create() method sends the message and returns a Message object that contains the message details, such as the message SID.

To use this TwilioService class, you need to replace the placeholder values for accountSid, authToken, and fromNumber with your Twilio account SID, authentication token, and phone number found on the Twilio console. Then, you can then create an instance of this class in your application and call the sendMessage() method to send SMS messages using the Twilio API.

Write the controllers

All controllers would be placed within the web subpackage. Inside this directory, create a file named EcoChallengeController.java and paste the code below. In this article, you have put controllers for the User and EcoTask models in the same file. It is advisable that you split these up into separate files as your requirement or project demands.

import com.twilio.earthday.earth.pieceone.model.EcoTask;
import com.twilio.earthday.earth.pieceone.model.EcoTaskRequest;
import com.twilio.earthday.earth.pieceone.model.User;
import com.twilio.earthday.earth.pieceone.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/eco-challenge")
public class EcoChallengeController {

   @Autowired
   private UserService userService;

   @PostMapping("/user")
   public ResponseEntity<User> createUser(@RequestBody User user) {
       return new ResponseEntity<>(userService.addUser(user), HttpStatus.CREATED);
   }

   @PostMapping("/task")
   public ResponseEntity<EcoTask> createTask(@RequestBody EcoTaskRequest ecoTaskRequest) {
       return new ResponseEntity<>(userService.addEcoTask(ecoTaskRequest), HttpStatus.CREATED);
   }

   @PutMapping("/task/{id}")
   public EcoTask completeEcoTask(@PathVariable("id") Long taskId) {
       return userService.completeEcoTask(taskId);
   }

   @PostMapping("/{taskId}/send-updates")
   public ResponseEntity<String> sendTaskUpdates(@PathVariable Long taskId) {
       userService.sendTaskUpdates(taskId);
       return ResponseEntity.ok("Task updates sent successfully");
   }

   @PostMapping("/user/{userId}/follower")
   public ResponseEntity<User> followUser(@PathVariable Long userId, @RequestParam String follower) {
       User user = userService.followUser(userId, follower);
       return ResponseEntity.ok(user);
   }

   @PostMapping("/user/{userId}/reminders")
   public ResponseEntity<String> sendReminders(@PathVariable Long userId) {
       userService.sendReminders(userId);
       return ResponseEntity.ok("Reminders sent successfully");
   }

   @PostMapping("/user/{userId}/milestone")
   public ResponseEntity<String> sendMilestoneNotification(@PathVariable Long userId, @RequestBody EcoTask ecoTask) {
       userService.sendMilestoneNotification(userId, ecoTask);
       return ResponseEntity.ok("Milestone notification sent successfully");
   }
}

The @PostMapping("/user") annotation specifies that this endpoint will handle HTTP POST requests with the URL path "/user". The @RequestBody annotation is used to indicate that the request body should be parsed as a User object. The createUser() method calls the addUser() method from the userService object and returns a ResponseEntity object that wraps the newly created User object and an HttpStatus of CREATED.

The @PostMapping("/task") annotation specifies that this endpoint will handle HTTP POST requests with the URL path "/task". The method takes two parameters, task and userId, which are parsed from the request body as a EcoTask object and a Long integer respectively. The createTask() method calls the addEcoTask() method from the userService object with the task and userId parameters, and returns a ResponseEntity object that wraps the newly created EcoTask object and a HttpStatus of CREATED.

The @PutMapping("/{id}") annotation specifies that this endpoint will handle HTTP PUT requests with the URL path "/{id}" where {id} is a placeholder for the id variable. The @PathVariable("id") annotation is used to indicate that the id variable should be parsed from the URL path. The completeEcoTask() method calls the completeEcoTask() method from the userService object with the taskId parameter and returns the completed EcoTask object.

The @PostMapping("/{taskId}/send-updates") annotation specifies that this endpoint will handle HTTP POST requests with the URL path "/{taskId}/send-updates" where {taskId} is a placeholder for the taskId variable. The @PathVariable annotation is used to indicate that the taskId variable should be parsed from the URL path. The sendTaskUpdates() method calls the sendTaskUpdates() method from the userService object with the taskId parameter and returns a ResponseEntity object with a string message and an HttpStatus of OK.

These endpoints can be used to create, update, and retrieve user data and eco tasks from a front-end application or any other client that can make HTTP requests.

Run the application

Now, the app will be tested to see that it works as expected. On the top-right corner of your IDE, click the play button to run the project. You should see a message on your IDE’s terminal reading “Started application…”.

Open Postman to try out the endpoints created in the controller class. Postman is a platform for testing APIs. In this section, you will create a dummy user, a dummy task, and update the task to completed status. As practice, you can try to send notifications to the user on this achievement. You will see that the app works!

Create a user

Open your Postman application, navigate to the URL box, and paste the URL “http://localhost:8088/api/v1/eco-challenge/user”. Click on the Body tab. Underneath it, select raw and the extreme right of the same list of options, click the dropdown and select JSON. In the text area, paste the following snippet:

{
   "firstName": "Dummy",
   "lastName": "Test-User",
   "phoneNumber": "08012345668"
}

And click the send button. A dummy user would be successfully created.

postman screenshot for a post request for adding a user

Create a task

On Postman, make a POST request to the URL "http://localhost:8088/api/v1/eco-challenge/task" to create a sample task. The EcoTaskRequest is shown below:

{
   "taskName": "Water the grasses today",
   "userPhoneNumber": "08012345668"
}

Supply a name for the task and the phone number of the user, who is creating the task. The response you receive should be the created EcoTask object for the user with the phone number in that request body, as shown below:

create a sample task in postman with the post request

Please note that exceptions for non-existent users have not been handled in this tutorial. In your real-world application, this should be among many exceptions you will be handling.

Update the task

Let us update the status of this EcoTask to completion. When a task is created for a user, it is incomplete by default. Therefore, the user would need to carry out the task before they can update this task to complete status.

To do this, you will need the ID of the task supplied from the section before as a path variable to the URL “http://localhost:8088/api/v1/eco-challenge/task/:taskId” as shown below.

Set this to a PUT request. The task is returned with a true statement for the completed field. This means that task has been successfully completed by the user.

update the task in postman with a put request

Follow a user

One of the ways for us to test that the SMS Service works is to “add followers” to the user. Go to Postman and paste the URL “http://localhost:8088/api/v1/eco-challenge/user/follower/add” in the URL bar. Set this request to POST. The request body requires the database ID and phone number of the user to whom the follower is to be added. An example is shown below:

{
    "userId": 3792158192853568805,
    "followerPhoneNumber": "08124711180"
}

When you click send, the operation will return the user object, with the updated followers list, displaying the newly-added follower.

update the list of followers in postman with a post request

Send notifications to user’s followers

It is time to let the user’s followers know about their task completion. Paste the link on your Postman URL bar “http://localhost:8088/api/v1/eco-challenge/user/<userId>/notification”. Replace <userId> with the user’s database ID. Make a POST request to the URL and notification messages will be sent to the user’s followers. The image below shows the request and the corresponding response, which is just a message in this case.

postman screenshot for a post request to send a notification to a user

The screenshot below showed the logged message with the message SID in the IDE console.

logged message with the message SID in the IDE console

And finally, a screenshot of the SMS sent to the mobile number is shown below:

sms text saying that your friend completed an EcoTask challenge

Add new features to a task challenge app

The app is open for even further improvements, depending on what is required per time. The code for this app can be found in this GitHub repository. For example, users might be inspired by other users on the app and want to follow them. Or they might need to be reminded intermittently about challenges they opted to complete.

You might also want to let users know how well they are doing on the app. A milestone tracker would be a great feature to implement. For these, you might create methods to: followUser(), sendReminder() and sendMilestoneNotification() in the UserService.java class and corresponding methods for them in the controller as shown below:

 // In the service class
public User followUser(Long userId, String follower) {
        User user = userRepository.findById(userId).orElse(null);
        assert user != null;
        user.getFollowers().add(follower);
        userRepository.save(user);

        return user;
    }

    public void sendReminders(Long userId) {
        User user = userRepository.findById(userId).orElse(null);
        assert user != null;
        for (String follower : user.getFollowers()) {
            twilioService.sendMessage(follower, "Don't forget to complete your eco-challenges today!");
        }
    }

    public void sendMilestoneNotification(Long userId, EcoTask ecoTask) {
        User user = userRepository.findById(userId).orElse(null);
        twilioService
                .sendMessage(user.getPhoneNumber(), "Congratulations! You have reached a milestone in your journey" +
                        " to become more environmentally conscious: " + ecoTask.getTaskName());
    }

Navigate to the EcoChallengeController.java file and add the following endpoints:

// in the controller class
 @PostMapping("/user/{userId}/follower")
    public ResponseEntity<User> followUser(@PathVariable Long userId, @RequestParam String follower) {
        User user = userService.followUser(userId, follower);
        return ResponseEntity.ok(user);
    }

    @PostMapping("/user/{userId}/reminders")
    public ResponseEntity<String> sendReminders(@PathVariable Long userId) {
        userService.sendReminders(userId);
        return ResponseEntity.ok("Reminders sent successfully");
    }

    @PostMapping("/user/{userId}/milestone")
    public ResponseEntity<String> sendMilestoneNotification(@PathVariable Long userId, @RequestBody EcoTask ecoTask) {
        userService.sendMilestoneNotification(userId, ecoTask);
        return ResponseEntity.ok("Milestone notification sent successfully");
    }

The app is functional enough to be connected with a frontend and ready for use. Our app allows users to take their places in the EcoChallenge, by allowing users to complete tasks that are beneficial to their environment, and the Earth, by extension. You built in Java and provided endpoints to which a frontend technology can connect.

Tolulope is a growing software engineer who loves coding and writing about what he does. When he's not coding, he enjoys his time as a disc jockey.