UI Groundwork for the Vortex Experience

A Step-by-Step Guide to Building a BLE LED Controller

Introduction

Welcome to Vortex App Guide, a step-by-step tutorial on how to build a Bluetooth Low Energy (BLE) LED Controller App from scratch. Whether you’re a complete beginner or an experienced developer, this guide will walk you through every step, explaining how each part of the app works. By the end of this guide, you will have a fully functional Android App that can":

  • Scan and Connect to BLE devices

  • Control LED brightness, colors, and animations

  • Save and apply custom colors

  • Handle multiple devices efficiently

This guide is designed to be as beginner-friendly as possible while also providing advanced insights. If you’re completely new to Android development, we strongly recommend reading the official documentation on the Android Developers website to familiarize yourself with Android Studio, XML layouts, and Java basics, before diving into this project.

Why this Guide?

  • We don’t just show you the code - we explain it in detail, line by line.

  • You’ll build everything from scratch, ensuring you fully understand how the app works.

  • The project is modular, allowing you to add your own features later.

  • A newer version of this project is available on our Github: [InsGitname], where you can see the full original app with the latest updates and commits.

How This Guide Is Structured

  1. Setting Up Your Project - Install Android Studio, create a new project, and configure dependencies.

  2. Building the User Interface - Start by designing the app layout files ( XML) before writing Java code.

  3. Implementing Bluetooth Scanning - Learn how to scan and connect to BLE devices.

  4. Controlling LED Colors and Brightness - Send commands to an LED strip using BLE.

  5. Implementing Animations - Allows users to select LED animations.

  6. Optimizing the User Experience - Navigation, UI improvements, and accessibility.

  7. Testing And Debugging - Ensuring your app runs smoothly.

  8. Final Steps - Suggestions for improvements and deploying your app.

Each section will first guide you through the UI design, followed by implementing the logic step by step. This ensures you understand why we’re creating certain variables and assigning them to layout elements.

Getting Started: Creating a new Android Studio Project

Before we dive into building the app, let’s set up a new project in Android Studio:

Step 1: Install Android Studio

If you haven’t already, download and install Android Studio from the official Android Developer site. Follow the installation steps according to your operating system.

Step 2: Create a New Project

  1. Open Android Studio and select New Project.

  2. Choose Empty Activity and click Next.

  3. Name Your project.

  4. Choose Java as the programming language.

  5. Set the Minimum SDK to API23 (Android 6.0 Marshmallow) or higher.

  6. Click Finish and wait for the project to load.

Step 3: Configure Dependencies

To enable BLE support and material components, open build.gradle (Module: app) and add the following dependencies:

  
    implementation 'com.github.skydoves:colorpickerview:2.3.0'
    implementation 'com.google.android.material:material:1.12.0'
    implementation 'com.github.bumptech.glide:glide:4.16.0'
  

Sync the project to apply the changes.

Step 4: Update AndroidManifest.xml

Your app needs permissions to use Bluetooth. Open AndroidManifest.xml and add the following lines inside the <manifest> tag:

  
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  

Additionally, declare that the app requires BLE support:

  
    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
  

Once the project is set up, we’ll start by designing the UI for the home screen.

Building the Main Screen Layout ( activity_main.xml )

Now that we have set up the project, it’s time to create the home screen layout. This screen will allow users to:

  1. View the connection status of their BLE device.

  2. Navigate to different parts of the app (Device Selection, Static Color, Animations).

  3. Adjust LED brightness using SeekBar .

Since the UI is crucial in making an app user-friendly, we’ll design activity_main.xml before implementing the logic in MainActivity.java .

Step 1: Locate or Create the File

  1. In Android Studio, open res/layout folder

  2. Find activity_main.xml . If it doesn’t exist, right-click on layout > New > Layout Resource File and name it activity_main.xml .

  3. Open activity_main.xml in Design or Code view.

Step 2: Designing the UI (XML Code)

We will now go through each part of the layout in detail to explain its purpose.

1. Root Layout ( Relative Layout )

  
    <RelativeLayout 
      xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="@color/backgroundColor">
  
  • This is the container that holds all the UI elements.

  • match_parent ensures that it fills the entire screen.

  • @color/backgroundColor sets the background color defined in colors.xml .

2. Header Title ( TextView )

  
    <TextView 
      android:id="@+id/headerTitle"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/app_name"
      android:textSize="24sp"
      android:textStyle="bold"
      android:textColor="@color/AccentColor"
      android:layout_marginTop="24dp"
      android:layout_marginStart="16dp"/>
  
  • displays the app’s name.

  • textSize=’24sp’ sets the text size.

  • id=”@+id/headerTitle” sets the name of the resource.

  • layout_width=”wrap_content” / layout_height=”wrap_content” sets the size to match the size of the contents inside.

  • text=”@string/app_name” sets the displayed text to a string resource located in strings.xml .

  • textStyle=”bold” sets the style of the text.

  • layout_marginTop=”24dp” / layout_marginStart=”16dp” sets the minimal amount of pixels os the resource from the top/Start.

3. Device Status Text ( TextView )

  
    <TextView 
      android:id="@+id/statusText"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/device_not_connected"
      android:textSize="16sp"
      android:textColor="@color/primaryTextColor"
      android:layout_below="@id/headerTitle"
      android:layout_marginTop="8dp"
      android:layout_marginStart="16dp"/>
  
  • Displays whether a device is connected or disconnected.

  • ”@string/device_not_connected” in this case is a default text for the resource.

  • layout_below=”@id/headerTitle” sets the TextView to always be below headerTitle.

4. Navigation Buttons (Animations, Static Color)

  
    <Button 
      android:id="@+id/staticColorBtn"
      android:layout_width="match_parent"
      android:layout_height="48dp"
      android:layout_below="@id/statusText"
      android:marginTop="16dp"
      android:marginHorizontal="16dp"
      android:text="@string/static_color_button"
      android:textColor="@color/primaryTextColor"
      android:background="@drawable/rounded_button"/>
  
  • This button allows user to do a certain thing, like switching activities once clicked.

  • These buttons have a fixed height so all the buttons look the same.

  • background=”@drawable/rounded_button” sets the look of the button to a certain resource.

  • marginHorizontal=”16dp” adds 16dp of spacing on both sides of the element.

5. ImageButton Inside a FrameLayout (Select Device)

  
    <FrameLayout 
      android:id="@+id/selectDeviceFrame"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:clickable="true"
      android:focusable="true"
      android:background="@drawable/button_background_with_outline"
      android:layout_alignParentEnd="true"
      android:layout_alignTop="@id/headerTitle"
      android:layout_marginEnd="16dp">

      <ImageButton 
        android:id="@+id/selectDeviceBtn"
        android:layout_width="70dp"
        android:layout_height="45dp"
        android:layout_gravity="center"
        android:contentDescription="@string/device_icon_desc"
        android:src="@drawable/ic_device"
        android:background="@null"/>

    </FrameLayout>
  

Why is it inside a FrameLayout ?

1. Positioning & Overlay Flexibility

  • FrameLayout allows the ImageButton to be easily positioned relative to other UI elements.

  • It makes it easier to overlay multiple elements, should we need to add icons, badges, or animated effects later.

2. Alignment to Parent Edge

  • The FrameLayout is aligned to the top-right corner ( alignParentEnd=”true” ).

  • This ensures the ImageButton is consistently positioned.

3. Scalability & Customization

  • Wrapping the ImageButton in FrameLayout makes it easier to add extra elements like notification badges in future updates.

  • If we want to expand its touch area, we can modify the FrameLayout without affecting other views.

Components

  • clickable=”true” Enables click interaction.

  • focusable=”true” Allows the button to receive focus.

  • layout_alignParentEnd=”true” aligns the FrameLayout with the end to the parent component.

  • layout_alignTop=”@id/headerTitle” aligns the component with the top of headerTitle.

  • layout_gravity=”center” Centers the ImageButton inside the FrameLayout .

  • src=”@drawable/ic_device” Loads the ic_device inside the button.

6. Brightness Control ( SeekBar )

Another crucial component in activity_main.xml is the SeekBar, which allows users to adjust the LED brightness.

  
    <SeekBar 
      android:id="@+id/brightnessSeekBar"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_above="@+id/bottomNavigation"
      android:layout_marginBottom="16dp"
      android:marginHorizontal="16dp"
      android:progress="128"
      android:valueFrom="48"
      android:valueTo="60"
      android:thumbTint="@color/AccentColor"
      android:progressTint="@color/AccentColor"/>
  
  • progrss=”128” Sets the default brightness to 50%.

  • thumbTint=”@color/AccentColor” Sets the slider thumb color.

  • progressTint=”@color/primaryTextColor” Sets the progress color.

  • valueFrom=”48” / valueTo=”60” Sets the max/min values of the SeekBar .

7. Botom Navigation ( BottomNavigationView )

The BottomNavigationView provides an easy way to switch between app screens.

  
    <com.google.android.material.bottomnavigation.BottomNavigationView 
      android:id="@+id/bottomNavigation"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_alignParentBottom="true"
      android:background="@color/SecondaryColor"
      app:menu="@menu/bottom_navigation_menu"
      app:itemIconTint="@color/AccentColor"
      app:itemTextColor="@color/primaryTextColor"/>
  
  • app:itemIconTint=”@color/AccentColor” Sets the icon colors.

  • app:itemTextColor="@color/navItemColor" Sets the text color.

  • app:menu="@menu/bottom_navigation_menu" References the menu resource file that defines navigation items.

Summary of the Work

In this section, we designed the entire UI layout for the main screen of the Vortex App using RelativeLayout . Here’s what we created:

  1. Header: A title TextView at the top to display the app name.

  2. Connection Status: A dynamic status TextView just below the header to reflect whether a BLE device is connected.

  3. Device Selector: a FrameLayout with an ImageButton styled and aligned to the right, letting users choose a device.

  4. Buttons: two custom buttons letting us access LED animations and static color selection.

  5. Brightness Control: A SeekBar allowing users to adjust LED intensity.

  6. Bottom Navigation Bar: A modern BottomNavigationView for easy screen navigation.

Each UI element was explained in detail with clear styling and layout logic. You now have a functional, consistent, and responsive design for the main screen.

Full activity_main code

▶ Reveal the full code
  
    <RelativeLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="@color/backgroundColor">

      <!-- Header Title -->
      <TextView
        android:id="@+id/headerTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/app_name"
        android:textSize="24sp"
        android:textStyle="bold"
        android:textColor="@color/AccentColor"
        android:layout_marginTop="24dp"
        android:layout_marginStart="16dp"/>

      <!-- Select Device Button (Top-Right Corner) -->
      <FrameLayout
        android:id="@+id/selectDeviceFrame"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="true"
        android:focusable="true"
        android:background="@drawable/button_background_with_outline"
        android:layout_alignParentEnd="true"
        android:layout_alignTop="@id/headerTitle"
        android:layout_marginEnd="16dp">

        <ImageButton
          android:id="@+id/selectDeviceBtn"
          android:layout_width="70dp"
          android:layout_height="45dp"
          android:layout_gravity="center"
          android:contentDescription="@string/device_icon_desc"
          android:src="@drawable/ic_device"
          android:background="@null"/>

      </FrameLayout>

      <!-- Connection Status -->
      <TextView
        android:id="@+id/statusText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/device_not_connected"
        android:textSize="16sp"
        android:textColor="@color/primaryTextColor"
        android:layout_below="@id/headerTitle"
        android:layout_marginTop="8dp"
        android:layout_marginStart="16dp"/>

      <!-- Animations Button -->
      <Button
        android:id="@+id/animationsBtn"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_below="@id/statusText"
        android:layout_marginTop="16dp"
        android:layout_marginHorizontal="16dp"
        android:text="@string/animationsButton"
        android:textColor="@color/primaryTextColor"
        android:background="@drawable/rounded_button"/>

      <!-- Static Color Button -->
      <Button
        android:id="@+id/staticColorBtn"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_below="@id/animationsBtn"
        android:layout_marginTop="12dp"
        android:layout_marginHorizontal="16dp"
        android:text="@string/static_color_button"
        android:textColor="@color/primaryTextColor"
        android:background="@drawable/rounded_button"/>

      <!-- Brightness SeekBar -->
      <SeekBar
        android:id="@+id/brightnessSeekBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/bottomNavigation"
        android:layout_marginBottom="16dp"
        android:layout_marginHorizontal="16dp"
        android:progress="128"
        android:valueFrom="48"
        android:valueTo="60"
        android:thumbTint="@color/AccentColor"
        android:progressTint="@color/AccentColor"/>

      <!-- Bottom Navigation -->
      <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="@color/SecondaryColor"
        app:menu="@menu/bottom_navigation_menu"
        app:itemIconTint="@color/AccentColor"
        app:itemTextColor="@color/primaryTextColor"/>

    </RelativeLayout>
  
Next
Next

Missing Resources