<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Srav Nayani</title>
    <description>The latest articles on Forem by Srav Nayani (@sravnay).</description>
    <link>https://forem.com/sravnay</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3533941%2F9a96831b-f10b-479c-a225-39cc1edcc26b.png</url>
      <title>Forem: Srav Nayani</title>
      <link>https://forem.com/sravnay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sravnay"/>
    <language>en</language>
    <item>
      <title>AI Agent Building Block: Native App Automation</title>
      <dc:creator>Srav Nayani</dc:creator>
      <pubDate>Thu, 09 Oct 2025 04:03:38 +0000</pubDate>
      <link>https://forem.com/sravnay/ai-agent-building-block-native-app-automation-43kh</link>
      <guid>https://forem.com/sravnay/ai-agent-building-block-native-app-automation-43kh</guid>
      <description>&lt;p&gt;&lt;strong&gt;Code for this article is available at &lt;a href="https://github.com/shravyanayani/automation" rel="noopener noreferrer"&gt;https://github.com/shravyanayani/automation&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Artificial Intelligence
&lt;/h2&gt;

&lt;p&gt;Artificial Intelligence (AI) refers to a computer system's ability to perform tasks that usually need human intelligence. These tasks include learning, reasoning, problem-solving, and understanding language. &lt;/p&gt;

&lt;p&gt;The key components of AI are data, algorithms, and models. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Data provides the examples or information that AI learns from. Algorithms are the step-by-step methods that process this data to find patterns or make decisions. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The model is the outcome of the trained algorithms. It uses what it has learned from the data to predict or act on new inputs. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;These components work together so AI systems can keep improving their performance and make smart decisions in real-world situations. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is AI Agent
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7swwvostvpqwlvf9dtq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7swwvostvpqwlvf9dtq.png" alt="Generic AI Agent Design" width="643" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An AI agent is a system that can sense its environment, make decisions, and act to reach specific goals, often without ongoing human help.  &lt;/p&gt;

&lt;p&gt;The key components of an AI agent include the perception module, decision-making module, and action module.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The perception module collects information from the environment using sensors or data inputs and makes sense of it.
&lt;/li&gt;
&lt;li&gt;The decision-making module uses algorithms or models to select the best action based on goals, rules, or past experiences.
&lt;/li&gt;
&lt;li&gt;Finally, the action module executes those decisions, and the agent continuously repeats this cycle to learn and improve over time. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is Native Desktop App Automation
&lt;/h2&gt;

&lt;p&gt;Native desktop app automation or native automation uses scripts to automatically perform tasks on native web apps, like Windows native application. This includes filling out forms, clicking buttons, scraping data, recognizing data, or testing applications without needing manual effort.  &lt;/p&gt;

&lt;p&gt;A key components of native automation is the native object detection technologies like windows object detection or OCR (Optical Character Recognition) based text detection or AI based pattern and object detection.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Native Object Detection&lt;/strong&gt; : Apps made with native technology or what ever high eve code that compiles into native code, such apps will have native operating system controls. For example the buttons on such windows apps use the native windows button object, and it can be detected and controlled using Windows SDK. For example Java AWT code when run on Windows OS uses native Windows controls. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OCR (Optical Character Recognition)&lt;/strong&gt; : OCR uses the patterns to detect the objects and characters on the screen. Such characters can be used to scrape or extract the text using code. Also the object detected on the screen can be used to control them like clicking, text entering etc.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI based pattern and object detection&lt;/strong&gt; : AI can be used for pattern detection of objects like specific button based on the text on the button, or associating the labes and controls by proximity etc.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the objects are identified then native mouse and keyboard api can be used to simulate human actions.  &lt;/p&gt;

&lt;p&gt;Apart from the object detection, the scripts are needed to perform actions on the controls simulating huma actions like mouse and keyboard actions. Once such building block scripts are available, they can be used for performing a high-level functionality towards specific goals.   &lt;/p&gt;

&lt;p&gt;These components enable developers to test, monitor, or interact with apps effectively and reliably.   &lt;/p&gt;

&lt;h2&gt;
  
  
  Native Automation vs API
&lt;/h2&gt;

&lt;p&gt;API (Application Programming Interface) integration lets an AI agent interact directly with an external system’s backend through structured requests. API integration is quick, efficient, and dependable. However, an API must exist for all external system integrations. Also, API access must be granted for the AI Agent.  &lt;/p&gt;

&lt;p&gt;Native Automation depends on UI based user functionality, so setting up the API interface is not necessary. However, there are some drawbacks to Native Automation, such as occasional unreliability, slowness, and complexity of the automation etc.   &lt;/p&gt;

&lt;h2&gt;
  
  
  Web Automation vs Native App Automation
&lt;/h2&gt;

&lt;p&gt;Web automation and native app automation involve using software to automatically test or interact with applications, but they target different platforms and use different tools. &lt;/p&gt;

&lt;p&gt;Web automation focuses on automating actions in web browsers, such as Chrome or Edge. It uses tools like Selenium or Playwright. This method interacts with web elements, including HTML, CSS, and JavaScript, through a web driver. It works across various browsers and operating systems. &lt;/p&gt;

&lt;p&gt;Native app automation, in contrast, targets mobile or desktop applications specifically designed for platforms like Android, iOS, or Windows. It employs tools like Appium, Espresso, or XCUITest, which communicate directly with the app’s native UI components instead of going through a browser. &lt;/p&gt;

&lt;p&gt;Web automation relies on the Web Browser's DOM (Document Object Model) while native app automation relies on UI elements defined by the operating system. &lt;/p&gt;

&lt;p&gt;In short, web automation tests websites, while native app automation tests standalone apps. Both ensure that software functions correctly in their respective environments. &lt;/p&gt;

&lt;h2&gt;
  
  
  How Native Automation can be a building block for AI Agents
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpk8p4qkfcoazyh3uph10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpk8p4qkfcoazyh3uph10.png" alt="Native Automation used with AI Agent" width="731" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Native automation can be one of the most powerful features of AI systems, as it basically allows them to perform actions on the native apps and interact with it much in the same way as a human user would do.  &lt;/p&gt;

&lt;p&gt;By the means of native automation, an AI agent can simply navigate through the apps, collect data, fill in the forms, or start a certain process — thus giving it the ability to access the up-to-date information and perform the tasks without any human intervention.  &lt;/p&gt;

&lt;p&gt;The automation layer is the one that physically does the clicking, typing, or scraping while the AI layer gives the intelligence – by deciding what to do, why, when, based on objectives or learned patterns.  &lt;/p&gt;

&lt;p&gt;As an illustration, an AI agent may employ natural language understanding to get an idea of a request (“purchase a stock at a specific price”) and then, through native automation, it goes to the respective app, compares the stock price, waits till the price condition is met and purchases the stock.  &lt;/p&gt;

&lt;p&gt;Ultimately, the combination of AI-driven decision-making and native automation execution gives agents the power to move seamlessly between thought and deed, thus implementing the smart insights to the world.   &lt;/p&gt;

&lt;h2&gt;
  
  
  Technology Choices for Native Automation
&lt;/h2&gt;

&lt;p&gt;Technology options for the purpose of native automation are primarily dependent on the to-be-executed tasks, the platforms to be targeted, and the degree of the intelligence or scalability desired.  &lt;/p&gt;

&lt;p&gt;Programming languages, support tools, and automation frameworks are the main classes of technologies that feature.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automation Frameworks&lt;/strong&gt; – Tools of this kind such as Native SDK based, OCR based, AI based are the most cited ones.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native SDK is the most fundamental and comparatively most reliable technology of all listed here.
&lt;/li&gt;
&lt;li&gt;OCR based object and text detection works when the app doesn't use the native objects. For example, if the UI is built using Java Swing API, the controls are not the native ones, but the Swing API draws the controls with low-level drawing operations. In such cases Native SDK cannot help to detect the UI controls, so OCR helps to identify the shapes and the coordinates of the controls.
&lt;/li&gt;
&lt;li&gt;AI based object detection goes 1 step further than simple OCR. It could perform in fault tolerant way like if the object got renamed or moved around. AI can still figure out the objects with its intelligence like humans.
&lt;/li&gt;
&lt;li&gt;A combination of these techniques can be used to build reliable automation systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Programming Languages&lt;/strong&gt; – The usual picks are Python, Java, JavaScript, and C# and the choice depends on the proficiency of the development team and integration requirements.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supporting Tools&lt;/strong&gt; – The use of some scheduler or some trigger to run the automation conditionally for smart maintenance is common in the frameworks which most often integrate.  &lt;/p&gt;

&lt;p&gt;These are not competing technologies but complementary ones — the language instructs the logic, the framework interacts with the apps, and the tools allow for integration with the environment.   &lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges with Native Automation
&lt;/h2&gt;

&lt;p&gt;Here are a few key challenges with native automation:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Non-Native Elements&lt;/strong&gt; – Apps built using non-native SDK are difficult to automate, in that case the pattern-based object detection must be used like OCR, AI etc.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform Compatibility&lt;/strong&gt; – Apps built for specific platform are generally not compatible on other platforms, so are the automation scripts can be used across multiple platforms. If apps are built using platform neutral technologies like Java Swing, special purpose tools are needed to automate such tools.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synchronization Issues&lt;/strong&gt; – The "element not found" errors may appear if the elements are not detected even a fraction of a second earlier than the script so proper waiting or timing control should be used.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance Overhead&lt;/strong&gt; – In the situation when a app layout or functionality has changed then there is a necessity for the test scripts to be updated first before the tests can run.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication and Security Barriers&lt;/strong&gt; – A few examples of the problematic issues that can arise automation due to the introduction of new security features like MFA (Multi Factor Authentication) etc.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability and Performance&lt;/strong&gt; – The large-scale automation process (e.g., running parallel tests) can require a lot of resources and a well-thought-out infrastructure.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handling Non-Standard Elements&lt;/strong&gt; – Just like regular UI components, complex ones can also be hard to automate. These components are canvas, pop-up, drag-and-drop, etc. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These challenges make app automation challenging sometimes but thoughtful design, robust frameworks, and continuous maintenance makes it powerful.   &lt;/p&gt;

&lt;h2&gt;
  
  
  Coding sample Native Automation using AutoIT
&lt;/h2&gt;

&lt;p&gt;Following is the windows app automation code written in AutoIT Basic like script. The app automated is a brokerage app that provides the stock quote info including price and volume info. The script polls for the price and volume info, to decide if all the conditions to purchase stock is met. Practically the decision making can be handed off to AI engine. In that case the automation script will provide the automation services to the AI agent so that the tools share the overall responsibilities and they do the best they can do. This is written just for educational purposes. This script cannot be used for real usage, because the real-world usage of the stocks application need more sophisticated logic and a lot of reinforcement for proper handling of financial info.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include &amp;lt;AutoItConstants.au3&amp;gt;
#include &amp;lt;MsgBoxConstants.au3&amp;gt;
#include &amp;lt;WindowsConstants.au3&amp;gt;
#include &amp;lt;GUIConstantsEx.au3&amp;gt;

; Configuration Variables
Global $SYMBOL = "AAPL"              ; Stock symbol to monitor
Global $TARGET_PRICE = 150.00        ; Target price to trigger buy
Global $TARGET_VOLUME = 100000       ; Minimum volume required
Global $SHARES_TO_BUY = 100          ; Number of shares to buy
Global $CHECK_INTERVAL = 30          ; Seconds between each check
Global $FIDELITY_WINDOW = "Fidelity Active Trader Pro"  ; Main window title

; Function to check if Fidelity application is running
Func IsFidelityRunning()
    If WinExists($FIDELITY_WINDOW) Then
        Return True
    Else
        MsgBox($MB_ICONERROR, "Error", "Fidelity Active Trader Pro is not running!")
        Return False
    EndIf
EndFunc

; Function to activate Fidelity window
Func ActivateFidelity()
    If Not WinActivate($FIDELITY_WINDOW) Then
        Return False
    EndIf
    Sleep(1000)  ; Wait for window to activate
    Return True
EndFunc

; Function to enter symbol
Func EnterSymbol()
    ; Activate symbol input field (you'll need to adjust coordinates)
    MouseClick("left", 100, 100)
    Sleep(500)
    Send($SYMBOL)
    Sleep(500)
    Send("{ENTER}")
    Sleep(1000)
EndFunc

; Function to get current price
Func GetCurrentPrice()
    ; You'll need to adjust coordinates based on where price appears
    Local $price = PixelGetColor(200, 200)  ; Replace with actual coordinates
    ; Add OCR logic here to read price
    Return $price
EndFunc

; Function to get current volume
Func GetCurrentVolume()
    ; You'll need to adjust coordinates based on where volume appears
    Local $volume = PixelGetColor(300, 200)  ; Replace with actual coordinates
    ; Add OCR logic here to read volume
    Return $volume
EndFunc

; Function to place buy order
Func PlaceBuyOrder()
    ; Click Trade button
    MouseClick("left", 400, 100)  ; Adjust coordinates
    Sleep(1000)

    ; Enter number of shares
    Send($SHARES_TO_BUY)
    Sleep(500)

    ; Click Buy button
    MouseClick("left", 500, 150)  ; Adjust coordinates
    Sleep(1000)

    ; Confirm order
    MouseClick("left", 550, 200)  ; Adjust coordinates
    Sleep(1000)
EndFunc

; Main monitoring loop
While 1
    If Not IsFidelityRunning() Then
        Exit
    EndIf

    If Not ActivateFidelity() Then
        ContinueLoop
    EndIf

    EnterSymbol()

    Local $currentPrice = GetCurrentPrice()
    Local $currentVolume = GetCurrentVolume()

    ConsoleWrite("Current Price: " &amp;amp; $currentPrice &amp;amp; " Volume: " &amp;amp; $currentVolume &amp;amp; @CRLF)

    ; Check if conditions are met
    If $currentPrice &amp;lt;= $TARGET_PRICE And $currentVolume &amp;gt;= $TARGET_VOLUME Then
        PlaceBuyOrder()
        MsgBox($MB_ICONINFORMATION, "Order Placed", "Buy order placed for " &amp;amp; $SHARES_TO_BUY &amp;amp; " shares of " &amp;amp; $SYMBOL)
        Exit
    EndIf

    Sleep($CHECK_INTERVAL * 1000)
WEnd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Coding sample Native Automation using PyWinAuto
&lt;/h2&gt;

&lt;p&gt;Following is the windows app automation code written in Python using PyWinAuto library. The app automated is a brokerage app that provides the stock quote info including price and volume info. The script polls for the price and volume info, to decide if all the conditions to purchase stock is met. Practically the decision making can be handed off to AI engine. In that case the automation script will provide the automation services to the AI agent so that the tools share the overall responsibilities and they do the best they can do. This is written just for educational purposes. This script cannot be used for real usage, because the real-world usage of the stocks application need more sophisticated logic and a lot of reinforcement for proper handling of financial info.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import os
import time
import logging
from datetime import datetime
from dotenv import load_dotenv
from pywinauto.application import Application
from pywinauto.keyboard import send_keys
import cv2
import numpy as np
import pytesseract
from PIL import ImageGrab

# Load environment variables
load_dotenv()

# Configure logging
logging.basicConfig(
    filename='fidelity_trader.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

class FidelityTrader:
    def __init__(self):
        # Trading Configuration
        self.symbol = os.getenv('STOCK_SYMBOL', 'AAPL')
        self.target_price = float(os.getenv('TARGET_PRICE', '150.0'))
        self.target_volume = int(os.getenv('TARGET_VOLUME', '100000'))
        self.shares_to_buy = int(os.getenv('SHARES_TO_BUY', '100'))
        self.check_interval = int(os.getenv('CHECK_INTERVAL', '30'))

        # Application Configuration
        self.app_path = os.getenv('FIDELITY_APP_PATH', '')
        self.window_title = "Fidelity Active Trader Pro"
        self.app = None
        self.main_window = None

    def connect_to_application(self):
        """Connect to Fidelity application"""
        try:
            # Try to connect to running instance
            self.app = Application(backend="uia").connect(title=self.window_title)
            logging.info("Connected to existing Fidelity application")
        except Exception as e:
            try:
                # Launch new instance if not running
                self.app = Application(backend="uia").start(self.app_path)
                logging.info("Launched new Fidelity application")
            except Exception as launch_error:
                logging.error(f"Failed to launch Fidelity: {launch_error}")
                raise

        self.main_window = self.app.window(title=self.window_title)
        self.main_window.set_focus()

    def capture_region(self, region):
        """Capture a specific region of the screen"""
        screenshot = ImageGrab.grab(bbox=region)
        return np.array(screenshot)

    def get_text_from_image(self, image):
        """Extract text from image using OCR"""
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        text = pytesseract.image_to_string(gray, config='--psm 6')
        return text.strip()

    def get_price(self):
        """Get current stock price from the application"""
        try:
            # Adjust coordinates based on where price appears in the application
            price_region = (100, 100, 200, 130)  # Example coordinates
            price_image = self.capture_region(price_region)
            price_text = self.get_text_from_image(price_image)
            return float(price_text.replace('$', '').strip())
        except Exception as e:
            logging.error(f"Error getting price: {e}")
            return None

    def get_volume(self):
        """Get current trading volume from the application"""
        try:
            # Adjust coordinates based on where volume appears in the application
            volume_region = (300, 100, 400, 130)  # Example coordinates
            volume_image = self.capture_region(volume_region)
            volume_text = self.get_text_from_image(volume_image)
            return int(volume_text.replace(',', '').strip())
        except Exception as e:
            logging.error(f"Error getting volume: {e}")
            return None

    def enter_symbol(self):
        """Enter stock symbol in the application"""
        try:
            # Find and click symbol input field
            symbol_input = self.main_window.child_window(auto_id="symbolInput")
            symbol_input.click_input()
            send_keys(self.symbol)
            send_keys('{ENTER}')
            logging.info(f"Entered symbol: {self.symbol}")
        except Exception as e:
            logging.error(f"Error entering symbol: {e}")
            raise

    def place_buy_order(self):
        """Place a buy order"""
        try:
            # Click trade button
            trade_button = self.main_window.child_window(title="Trade")
            trade_button.click_input()

            # Enter number of shares
            shares_input = self.main_window.child_window(auto_id="sharesInput")
            shares_input.type_keys(str(self.shares_to_buy))

            # Click buy button
            buy_button = self.main_window.child_window(title="Buy")
            buy_button.click_input()

            # Confirm order
            confirm_button = self.main_window.child_window(title="Confirm")
            confirm_button.click_input()

            logging.info(f"Buy order placed for {self.shares_to_buy} shares of {self.symbol}")
            return True
        except Exception as e:
            logging.error(f"Error placing buy order: {e}")
            return False

    def monitor_stock(self):
        """Main monitoring loop"""
        logging.info(f"Starting monitoring for {self.symbol}")
        logging.info(f"Target price: ${self.target_price}")
        logging.info(f"Target volume: {self.target_volume}")

        while True:
            try:
                self.enter_symbol()
                current_price = self.get_price()
                current_volume = self.get_volume()

                if current_price and current_volume:
                    logging.info(f"Current price: ${current_price}, Volume: {current_volume}")

                    if current_price &amp;lt;= self.target_price and current_volume &amp;gt;= self.target_volume:
                        if self.place_buy_order():
                            logging.info("Order executed successfully")
                            break
                        else:
                            logging.error("Failed to execute order")

                time.sleep(self.check_interval)

            except Exception as e:
                logging.error(f"Error in monitoring loop: {e}")
                time.sleep(self.check_interval)

if __name__ == "__main__":
    try:
        trader = FidelityTrader()
        trader.connect_to_application()
        trader.monitor_stock()
    except Exception as e:
        logging.critical(f"Critical error: {e}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing Native Automation
&lt;/h2&gt;

&lt;p&gt;Testing native automation code is vital if you want to make sure that it functions consistently and is capable of dealing with the situations that might appear in the world.  &lt;/p&gt;

&lt;p&gt;Part of web automation testing can consist of the use of assertions for verification that expected elements have appeared, data is accurate, and navigational steps have been completed successfully.  &lt;/p&gt;

&lt;p&gt;Moreover, a wait conditions can be used for handling dynamic data loads instead of using fixed delays, which is also a very effective method.  &lt;/p&gt;

&lt;p&gt;Try your automated actions on various browsers and devices to check whether the performance is the same.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating Native Automation with AI Agent
&lt;/h2&gt;

&lt;p&gt;A script for native automation is excellent for handling repetitive and predictable tasks, such as clicking on buttons, filling out forms, moving through pages, or extracting data.  &lt;/p&gt;

&lt;p&gt;Conversely, an AI agent is capable of logical thinking, planning, learning, and making judgments based on available data.  &lt;/p&gt;

&lt;p&gt;When you combine these two, you get a smart system where the AI is in charge of the operations and the automation carries out the tasks. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typical AI Agent System Components&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI Decision Engine&lt;/strong&gt; - Processes goals, rules, or user commands and decides the sequence of actions. Can use ML models, NLP, or rule-based systems
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native Automation Layer&lt;/strong&gt; - Executes low-level actions on apps via tools like AutoIT, PyWinAuto or WinDriver. Handles clicks, inputs, scrolling, navigation.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perception / Data Extraction Module&lt;/strong&gt; - Observes the web environment and extracts relevant information (prices, flight options, stock data). Feeds this back to the AI agent.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback / Learning Module&lt;/strong&gt; - Evaluates outcomes of automated actions (e.g., did the AI decision the purchase of stocks based on the conditions?) and updates decision-making models for future improvements.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduler / Controller&lt;/strong&gt; - Coordinates the flow: triggers web automation when needed, handles retries, logs progress, and ensures proper sequencing of tasks. &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>agents</category>
    </item>
    <item>
      <title>AI Agent Building Block: Web Automation</title>
      <dc:creator>Srav Nayani</dc:creator>
      <pubDate>Wed, 08 Oct 2025 23:52:46 +0000</pubDate>
      <link>https://forem.com/sravnay/ai-agent-building-block-web-automation-kc3</link>
      <guid>https://forem.com/sravnay/ai-agent-building-block-web-automation-kc3</guid>
      <description>&lt;p&gt;&lt;strong&gt;Code for this article is available at &lt;a href="https://github.com/shravyanayani/automation" rel="noopener noreferrer"&gt;https://github.com/shravyanayani/automation&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Artificial Intelligence
&lt;/h2&gt;

&lt;p&gt;Artificial Intelligence (AI) refers to a computer system's ability to perform tasks that usually need human intelligence. These tasks include learning, reasoning, problem-solving, and understanding language. &lt;/p&gt;

&lt;p&gt;The key components of AI are data, algorithms, and models. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Data provides the examples or information that AI learns from. Algorithms are the step-by-step methods that process this data to find patterns or make decisions. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The model is the outcome of the trained algorithms. It uses what it has learned from the data to predict or act on new inputs. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;These components work together so AI systems can keep improving their performance and make smart decisions in real-world situations. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is AI Agent
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7swwvostvpqwlvf9dtq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7swwvostvpqwlvf9dtq.png" alt="Generic AI Agent Design" width="643" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An AI agent is a system that can sense its environment, make decisions, and act to reach specific goals, often without ongoing human help.  &lt;/p&gt;

&lt;p&gt;The key components of an AI agent include the perception module, decision-making module, and action module.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The perception module collects information from the environment using sensors or data inputs and makes sense of it.
&lt;/li&gt;
&lt;li&gt;The decision-making module uses algorithms or models to select the best action based on goals, rules, or past experiences.
&lt;/li&gt;
&lt;li&gt;Finally, the action module executes those decisions, and the agent continuously repeats this cycle to learn and improve over time. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is Web Automation
&lt;/h2&gt;

&lt;p&gt;Web automation uses software or scripts to automatically perform tasks on websites. This includes filling out forms, clicking buttons, scraping data, or testing web applications without needing manual effort.  &lt;/p&gt;

&lt;p&gt;The key components of web automation are the web driver or automation tool, like Selenium, the scripts or test code, and the browser interface. The scripts instruct the automation tool on which actions to take. The automation tool then controls the browser to carry out those actions, while the browser interface shows and responds like a human user would.  &lt;/p&gt;

&lt;p&gt;These components enable developers to test, monitor, or interact with websites effectively and reliably. &lt;/p&gt;

&lt;h2&gt;
  
  
  Web Automation vs API
&lt;/h2&gt;

&lt;p&gt;API (Application Programming Interface) integration lets an AI agent interact directly with an external system’s backend through structured requests. API integration is quick, efficient, and dependable. However, an API must exist for all external system integrations. Also, API access must be granted for the AI Agent. &lt;/p&gt;

&lt;p&gt;Web Automation depends on UI based user functionality, so setting up the API interface is not necessary. However, there are some drawbacks to Web Automation, such as occasional unreliability, slowness, and websites disabling the automation etc. &lt;/p&gt;

&lt;h2&gt;
  
  
  Web Automation vs Native App Automation
&lt;/h2&gt;

&lt;p&gt;Web automation and native app automation involve using software to automatically test or interact with applications, but they target different platforms and use different tools. &lt;/p&gt;

&lt;p&gt;Web automation focuses on automating actions in web browsers, such as Chrome or Edge. It uses tools like Selenium or Playwright. This method interacts with web elements, including HTML, CSS, and JavaScript, through a web driver. It works across various browsers and operating systems. &lt;/p&gt;

&lt;p&gt;Native app automation, in contrast, targets mobile or desktop applications specifically designed for platforms like Android, iOS, or Windows. It employs tools like Appium, Espresso, or XCUITest, which communicate directly with the app’s native UI components instead of going through a browser. &lt;/p&gt;

&lt;p&gt;Web automation relies on the Web Browser's DOM (Document Object Model) while native app automation relies on UI elements defined by the operating system. &lt;/p&gt;

&lt;p&gt;In short, web automation tests websites, while native app automation tests standalone apps. Both ensure that software functions correctly in their respective environments. &lt;/p&gt;

&lt;h2&gt;
  
  
  How Web Automation can be a building block for AI Agents
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz3mwmmlbbgsf9xppre6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz3mwmmlbbgsf9xppre6g.png" alt="Web Automation used within AI Agent" width="729" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Web automation can be one of the most powerful features of AI systems, as it basically allows them to perform actions on the web and interact with it much in the same way as a human user would do. &lt;/p&gt;

&lt;p&gt;By the means of web automation, an AI agent can simply navigate through the sites, collect data, fill in the forms, or start a certain process — thus giving it the ability to access the up-to-date information and perform the online tasks without any human intervention. &lt;/p&gt;

&lt;p&gt;The automation layer is the one that physically does the clicking, typing, or scraping while the AI layer gives the intelligence – by deciding what to do, why, when, based on objectives or learned patterns. &lt;/p&gt;

&lt;p&gt;As an illustration, an AI agent may employ natural language understanding to get an idea of a request (“book a flight to Austin”) and then, through web automation, it goes to the respective travel websites, compares the prices, and makes the booking. &lt;/p&gt;

&lt;p&gt;Ultimately, the combination of AI-driven decision-making and web automation execution gives agents the power to move seamlessly between thought and deed, thus implementing the smart insights to the world. &lt;/p&gt;

&lt;h2&gt;
  
  
  Technology Choices for Web Automation
&lt;/h2&gt;

&lt;p&gt;Technology options for the purpose of web automation are primarily dependent on the to-be-executed tasks, the platforms to be targeted, and the degree of the intelligence or scalability desired. &lt;/p&gt;

&lt;p&gt;Programming languages, support tools, and automation frameworks are the main classes of technologies that feature. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automation Frameworks&lt;/strong&gt; – Tools of this kind such as Selenium, Playwright, Cypress, and Puppeteer are the most cited ones. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Selenium is compatible with different browsers and several languages (Java, Python, C#) besides. &lt;/li&gt;
&lt;li&gt;Playwright and Puppeteer are a bit quicker , more recent alternatives, where parallel testing and headless browsing are automatically supported. &lt;/li&gt;
&lt;li&gt;Cypress is the best choice for front-end developers using modern JavaScript frameworks. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Programming Languages&lt;/strong&gt; – The usual picks are Python, Java, JavaScript, and C# and the choice depends on the proficiency of the development team and integration requirements. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supporting Tools&lt;/strong&gt; – The use of some scheduler or some trigger to run the automation conditionally for smart maintenance is common in the frameworks which most often integrate. &lt;/p&gt;

&lt;p&gt;These are not competing technologies but complementary ones — the language instructs the logic, the framework interacts with the browser, and the tools allow for integration with the environment. &lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges with Web Automation
&lt;/h2&gt;

&lt;p&gt;Here are a few key challenges with web automation: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Web Elements&lt;/strong&gt; – Recently, JavaScript or AJAX are often used to update websites content, which automatically changes element IDs or structures and breaks automation scripts. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser Compatibility&lt;/strong&gt; – Every browser has a different way of rendering the same page that is almost negligible but still a bit different, thus scripts need to be tested in different browsers to be sure that they work consistently there. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synchronization Issues&lt;/strong&gt; – The "element not found" errors may appear if the elements are not loaded even a fraction of a second earlier than the script so proper waiting or timing control should be used. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance Overhead&lt;/strong&gt; – In the situation when a website layout or functionality has changed then there is a necessity for the test scripts to be updated first before the tests can run. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication and Security Barriers&lt;/strong&gt; – A few examples of the problematic issues that can arise automation due to the introduction of new security features such as captchas, multi-factor authentication, rate limits etc. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability and Performance&lt;/strong&gt; – The large-scale automation process (e.g., running parallel tests) can require a lot of resources and a well-thought-out infrastructure. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handling Non-Standard Elements&lt;/strong&gt; – Just like regular UI components, complex ones can also be hard to automate. These components are canvas, pop-up, drag-and-drop, etc. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These challenges make web automation challenging sometimes but thoughtful design, robust frameworks, and continuous maintenance makes it powerful. &lt;/p&gt;

&lt;h2&gt;
  
  
  Coding sample Web Automation
&lt;/h2&gt;

&lt;p&gt;Following is the Selenium web automation code written in Python language to search for flights on a travel site. This is written just for educational purposes.  This travel website cannot be used for real usage, because the websites are generally protected by no-bot usage policy, that will immediately come into picture with a captcha or a puzzle asking to prove that a human is using the site. Web Automation cannot pass this hurdle, so the automation script cannot be used without human supervision.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from datetime import datetime
import time

# Configuration
ORIGIN = "New York"  # Or airport code like "JFK"
DESTINATION = "Los Angeles"  # Or "LAX"
DEPARTURE_DATE = "15/10/2025"  # Format: DD/MM/YYYY (adjust based on site)

def setup_driver():
    """Set up Chrome driver in headless mode."""
    options = Options()
    options.add_argument("--headless")  # Run without UI
    options.add_argument("--no-sandbox")
    options.add_argument("--disable-dev-shm-usage")
    driver = webdriver.Chrome(options=options)
    return driver

def search_flights(driver):
    """Perform flight search."""
    wait = WebDriverWait(driver, 10)

    # Step 1: Navigate to Skyscanner
    driver.get("https://www.skyscanner.com/")
    time.sleep(2)  # Allow page load

    # Step 2: Select one-way trip (click if needed; Skyscanner defaults to round-trip, so toggle)
    try:
        one_way_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-testid="trip-type-selector-one-way"]')))
        one_way_button.click()
        time.sleep(1)
    except:
        print("One-way button not found; assuming default.")

    # Step 3: Enter origin
    origin_input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '[data-testid="origin-input"] input')))
    origin_input.clear()
    origin_input.send_keys(ORIGIN)
    time.sleep(1)

    # Click suggestion if appears (e.g., JFK)
    try:
        origin_suggestion = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-testid="suggestion-card"]')))
        origin_suggestion.click()
        time.sleep(1)
    except:
        print("No origin suggestion; proceeding.")

    # Step 4: Enter destination
    dest_input = driver.find_element(By.CSS_SELECTOR, '[data-testid="destination-input"] input')
    dest_input.clear()
    dest_input.send_keys(DESTINATION)
    time.sleep(1)

    # Click suggestion (e.g., LAX)
    try:
        dest_suggestion = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-testid="suggestion-card"]')))
        dest_suggestion.click()
        time.sleep(1)
    except:
        print("No destination suggestion; proceeding.")

    # Step 5: Enter departure date
    date_input = driver.find_element(By.CSS_SELECTOR, '[data-testid="date-picker"] input')
    date_input.clear()
    date_input.send_keys(DEPARTURE_DATE)
    time.sleep(1)

    # Select the date from calendar if pops up
    try:
        date_picker = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '[data-testid="date-picker-month"]')))
        # Find and click the specific date (adapt XPath for day)
        specific_date = driver.find_element(By.XPATH, f"//td[@data-testid='day-15']")  # Adjust for month/year
        specific_date.click()
        time.sleep(1)
    except:
        print("Date input direct; calendar may not have triggered.")

    # Step 6: Click search button
    search_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '[data-testid="search-button"]')))
    search_button.click()
    time.sleep(5)  # Allow results to load

def extract_lowest_price(driver):
    """Extract flight prices and find the lowest."""
    wait = WebDriverWait(driver, 10)
    flights = []

    try:
        # Wait for results to load
        wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '[data-testid="flight-result"]')))

        # Extract all flight cards
        flight_cards = driver.find_elements(By.CSS_SELECTOR, '[data-testid="flight-result"]')

        for card in flight_cards[:10]:  # Limit to top 10 for brevity
            try:
                price_elem = card.find_element(By.CSS_SELECTOR, '[data-testid="price"]')
                price_text = price_elem.text.strip().replace('$', '').replace(',', '')
                if price_text.isdigit():
                    price = int(price_text)
                    airline = card.find_element(By.CSS_SELECTOR, '[data-testid="airline"]').text
                    flights.append({'airline': airline, 'price': price})
            except:
                continue

        if flights:
            lowest = min(flights, key=lambda x: x['price'])
            print(f"Lowest priced flight: {lowest['airline']} for ${lowest['price']}")
            return lowest
        else:
            print("No prices extracted.")
            return None
    except Exception as e:
        print(f"Error extracting prices: {e}")
        return None

# Main execution
if __name__ == "__main__":
    driver = setup_driver()
    try:
        search_flights(driver)
        lowest_flight = extract_lowest_price(driver)
        if lowest_flight:
            print(f"Found lowest flight: {lowest_flight}")
        else:
            print("No flights found or error in extraction.")
    finally:
        driver.quit()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing Web Automation
&lt;/h2&gt;

&lt;p&gt;Testing web automation code is vital if you want to make sure that it functions consistently and is capable of dealing with the situations that might appear in the world. &lt;/p&gt;

&lt;p&gt;Part of web automation testing can consist of the use of assertions for verification that expected elements have appeared, data is accurate, and navigational steps have been completed successfully. &lt;/p&gt;

&lt;p&gt;Moreover, a wait condition (such as WebDriverWait) can be used for handling dynamic page loads instead of using fixed delays, which is also a very effective method. &lt;/p&gt;

&lt;p&gt;Try your automated actions on various browsers and devices to check whether the performance is the same. &lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating  Web Automation with AI Agent
&lt;/h2&gt;

&lt;p&gt;A script for web automation is excellent for handling repetitive and predictable tasks, such as clicking on buttons, filling out forms, moving through pages, or extracting data. &lt;/p&gt;

&lt;p&gt;Conversely, an AI agent is capable of logical thinking, planning, learning, and making judgments based on available data. &lt;/p&gt;

&lt;p&gt;When you combine these two, you get a smart system where the AI is in charge of the operations and the automation carries out the tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typical AI Agent System Components&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI Decision Engine&lt;/strong&gt; - Processes goals, rules, or user commands and decides the sequence of actions. Can use ML models, NLP, or rule-based systems &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Automation Layer&lt;/strong&gt; - Executes low-level actions on web pages via tools like Selenium, Playwright, or Puppeteer. Handles clicks, inputs, scrolling, navigation. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perception / Data Extraction Module&lt;/strong&gt; - Observes the web environment and extracts relevant information (prices, flight options, stock data). Feeds this back to the AI agent. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback / Learning Module&lt;/strong&gt; - Evaluates outcomes of automated actions (e.g., did the AI pick the lowest flight price?) and updates decision-making models for future improvements. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduler / Controller&lt;/strong&gt; - Coordinates the flow: triggers web automation when needed, handles retries, logs progress, and ensures proper sequencing of tasks. &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>automation</category>
    </item>
    <item>
      <title>Implementing Software Design Principles in a no-code tool, such as MIT App Inventor</title>
      <dc:creator>Srav Nayani</dc:creator>
      <pubDate>Sun, 28 Sep 2025 20:00:52 +0000</pubDate>
      <link>https://forem.com/sravnay/implementing-software-design-principles-in-a-no-code-environment-such-as-mit-app-inventor-3eg7</link>
      <guid>https://forem.com/sravnay/implementing-software-design-principles-in-a-no-code-environment-such-as-mit-app-inventor-3eg7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm64g3pqxbpymgnane2o6.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm64g3pqxbpymgnane2o6.PNG" alt="UI Design in MIT App Inventor" width="800" height="434"&gt;&lt;/a&gt;UI Design in MIT App Inventor&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbyov19wusna29jgaewpo.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbyov19wusna29jgaewpo.PNG" alt="Blocks to code in MIT App Inventor" width="800" height="421"&gt;&lt;/a&gt;Blocks to code in MIT App Inventor&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvcrw88h03uzrblfwz1j1.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvcrw88h03uzrblfwz1j1.PNG" alt="Parameterizing the UI widgets to not repeat the code" width="792" height="763"&gt;&lt;/a&gt;Parameterizing the UI widgets to not repeat the code&lt;/p&gt;

&lt;p&gt;Below are examples of Software Design Principles commonly applied when designing code in object-oriented languages like Java.   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single Responsibility Principle (SRP):&lt;/strong&gt; A class should have only one reason to change, meaning it should have a single, well-defined purpose.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open-Closed Principle (OCP):&lt;/strong&gt; Software entities (like classes and modules) should be open for extension but closed for modification.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Liskov Substitution Principle (LSP):&lt;/strong&gt; Objects of a superclass should be replaceable with objects of a subclass without affecting the program's correctness.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interface Segregation Principle (ISP):&lt;/strong&gt; Clients should not be forced to depend on interfaces they do not use; it's better to have many specific interfaces than one general-purpose interface.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Inversion Principle (DIP):&lt;/strong&gt; High-level modules should not depend on low-level modules; both should depend on abstractions, and abstractions should not depend on details, but details should depend on abstractions.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DRY (Don't Repeat Yourself):&lt;/strong&gt; Avoid duplicating code to reduce complexity and improve maintainability.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep It Simple:&lt;/strong&gt; Design systems to be as simple as possible, avoiding unnecessary complexity.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;YAGNI (You Aren't Gonna Need It):&lt;/strong&gt; Do not implement functionality until it is actually required, preventing the accumulation of unnecessary code.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Abstraction:&lt;/strong&gt; Hide complex details and provide a clear, understandable interface to users.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modularity:&lt;/strong&gt; Break down a software system into smaller, independent, and manageable components.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testability and Maintainability:&lt;/strong&gt; Design code that is easy to test, understand, and maintain by fixing bugs or adding new features.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusability:&lt;/strong&gt; Design software that can be used in other projects with minimal modification. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Above principles are straightforward to implement in object-oriented languages like Java. However, in a no-code environment such as MIT App Inventor, things can quickly become complex. While drag-and-drop, Lego-like code blocks make it easy to get started, the number of blocks can grow rapidly and become overwhelming as the application scales. &lt;/p&gt;

&lt;p&gt;This is where good code design and organization practices prove invaluable. They help developers manage complexity and build maintainable, real-world applications. In the following sections, I will demonstrate how the above design principles can be applied effectively within MIT App Inventor and other similar no-code systems. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single Responsibility Principle (SRP):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unlike traditional code-based environments, in MIT App Inventor, the Screen serves as the primary unit of code—similar to a class in Java. Each Screen has its own properties, contains child UI components arranged in a tree-like hierarchy, and is controlled by Lego-style blocks that manage both UI events and backend services such as CloudDB. &lt;/p&gt;

&lt;p&gt;Because there is no built-in mechanism for reusing UI elements across different Screens, it is best to design each Screen with a single responsibility. This approach keeps the widget hierarchy and associated blocks simple, organized, and easier to maintain. &lt;/p&gt;

&lt;p&gt;When a Screen must serve multiple purposes with only minor variations, you can adopt a delegation model. By introducing a flag to indicate the current mode, the Screen’s behavior can be adjusted without duplicating the entire structure. However, avoid applying this technique to Screens with vastly different roles, as overuse can increase complexity and make the app harder to maintain. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open-Closed Principle (OCP):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In object-oriented programming (OOP) languages like Java, extension is typically achieved through inheritance. In no-code environments, achieving the same effect is more challenging. &lt;/p&gt;

&lt;p&gt;A recommended practice is to design your blocks and UI hierarchy so that new functionality can be added as an extension, rather than modifying existing features. Avoid altering existing behavior, as this can introduce errors and compromise stability. By extending instead of modifying, you ensure your app remains reliable while still allowing it to evolve over time. &lt;/p&gt;

&lt;p&gt;Additionally, the Open-Closed Principle can be applied when creating custom components, enabling them to be extended without changing their original implementation. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Liskov Substitution Principle (LSP):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since MIT App Inventor does not directly support inheritance, the concepts of subclass and superclass can only be implemented through custom components. In this approach, a subclass can be used wherever its superclass would normally appear. For example, a specialized Button component can be created and substituted for a default Button, adhering to this principle. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interface Segregation Principle (ISP):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rather than creating a single screen or block setup that handles many unrelated features, break your app into smaller, purpose-driven modules. Develop utility procedures (functions) that perform one well-defined task instead of large, catch-all procedures. Similarly, design each screen to serve one primary purpose rather than bundling multiple features together. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependency Inversion Principle (DIP):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rather than tying your logic directly to a specific implementation, encapsulate it in a procedure that represents the abstract behavior. Use flags or configuration variables to switch between implementations when minor variations are needed. Additionally, create helper screens or components that act as reusable “providers” for shared functionality. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DRY (Don't Repeat Yourself):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If a sequence of blocks is used multiple times, extract it into a procedure. Use constants or configuration values stored in global variables instead of repeating them across screens. Avoid creating multiple screens that are nearly identical but differ only in minor ways; instead, repurpose a single screen and control variations with mode flags. For logic shared across multiple screens—such as file picking or error handling—consider creating a dedicated screen or custom component to centralize that functionality. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep It Simple:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Avoid creating too many screens; instead, reuse screens by using flags or parameters to modify behavior. Keep your blocks organized—don’t create spaghetti code, and avoid cramming multiple complex algorithms into a single block; separate them into smaller, manageable procedures. Similarly, don’t overload screens with too many buttons or labels; use clear labels and group controls logically for a clean and user-friendly interface. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;YAGNI (You Aren't Gonna Need It):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Avoid adding screens prematurely based on assumptions or anticipated features. Keep the UI minimal and allow it to evolve according to actual needs and requirements. Similarly, don’t over-generalize procedures; while it may be tempting to create a “super procedure” that handles every possible case, breaking logic into focused, well-defined procedures is more maintainable and easier to debug. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Abstraction:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use procedures to encapsulate logic instead of repeating long sequences of blocks. Abstract UI actions into events and triggers, and move all complex logic into dedicated procedures. Create reusable helper procedures to handle recurring or complex tasks. Additionally, keep data storage blocks separate from UI update blocks to maintain clarity and modularity. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modularity:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use multiple screens for different features, ensuring each screen represents a single main functionality or module. Define procedures as mini-modules that perform one specific task. Avoid mixing UI event handling (buttons, sliders) with data handling (TinyDB, lists). If a group of UI elements and blocks serves the same purpose in multiple places, create a custom component (using extensions or templates) for reuse. Finally, don’t overload a single screen with too many features; keep each screen focused and manageable. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testability:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Make screens as testable as possible by keeping them independent. Make data storage mockable by using intermediary procedures, which can also be leveraged for testing. Additionally, add debug labels or temporary notifiers to display intermediate results and facilitate easier debugging. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintainability:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use self-explanatory names for procedures, variables, and components. If you find yourself copy-pasting the same blocks multiple times, encapsulate them into a procedure. Add block comments to clarify any tricky logic. Since MIT App Inventor does not support direct Git integration, export your .aia files frequently and maintain versioned backups (v1, v2, v3). Consider using cloud storage to store these backups and snapshots of key features. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reusability:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Encapsulate commonly used operations into procedures instead of repeating blocks. Parameterize procedures rather than hardcoding values, so they can be reused in different contexts. Organize shared data access by creating wrapper procedures for TinyDB, CloudDB, file operations, and similar tasks. &lt;/p&gt;

&lt;p&gt;Since MIT App Inventor does not support “fragments” like Android, reuse screens for similar purposes with minor variations by using flags or parameters. Abstract repeated UI patterns: if multiple screens share a UI pattern (e.g., a “list of items + delete button”), copy the UI once and adjust the data source or title dynamically. &lt;/p&gt;

&lt;p&gt;Take advantage of extensions and community modules to implement reusable functionality, and export blocks or use the backpack feature to transfer procedures and components to other projects. &lt;/p&gt;

</description>
      <category>mobile</category>
      <category>designpatterns</category>
      <category>mitappinventor</category>
      <category>nocode</category>
    </item>
    <item>
      <title>Develop a native Android app : PDF Voice Reader</title>
      <dc:creator>Srav Nayani</dc:creator>
      <pubDate>Sat, 27 Sep 2025 21:11:18 +0000</pubDate>
      <link>https://forem.com/sravnay/develop-a-native-android-app-pdf-voice-reader-3ng3</link>
      <guid>https://forem.com/sravnay/develop-a-native-android-app-pdf-voice-reader-3ng3</guid>
      <description>&lt;p&gt;This project's full code is in GitHub @ &lt;a href="https://github.com/shravyanayani/AndroidPdfVoiceReader" rel="noopener noreferrer"&gt;https://github.com/shravyanayani/AndroidPdfVoiceReader&lt;/a&gt; &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ljxy44pwbjsnj81i35s.jpg" alt=" " width="230" height="512"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Why Choose Native Apps?
&lt;/h2&gt;

&lt;p&gt;Native apps on Android mobile devices are designed to take full advantage of the underlying platform’s features and capabilities. In this case, the Text-to-Speech (TTS) functionality of the phone can be seamlessly integrated, allowing the app to read PDF content aloud without limitations or external dependencies. &lt;/p&gt;

&lt;p&gt;Beyond feature access, Android native apps also excel in performance and responsiveness. Since they are optimized for the specific operating system, they can handle tasks like parsing text and generating speech more efficiently, resulting in a smoother, faster, and more reliable user experience. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why Create PDF Voice Reader?
&lt;/h2&gt;

&lt;p&gt;PDF is one of the most widely used document formats, valued for its portability across devices and operating systems. Books, research papers, articles, and even web pages or documents can easily be saved as PDFs, making them a universal standard for digital reading. &lt;/p&gt;

&lt;p&gt;While PDFs are convenient for distribution, reading them visually isn’t always practical or desirable. In many situations—such as while driving, before sleep, exercising, or when reducing screen time—having the document read aloud can be far more convenient. &lt;/p&gt;

&lt;p&gt;Although there are existing apps in app stores that provide PDF-to-speech functionality, many of them come with drawbacks. They often include intrusive advertisements and lack the customization options users truly need. For example, most do not allow skipping repetitive elements like headers, footers, or page numbers, which disrupt the listening experience. &lt;/p&gt;

&lt;p&gt;By creating PDF Voice Reader, these limitations can be overcome. The app not only eliminates ads but also offers greater flexibility, allowing users to tailor the reading experience to their needs. This makes it a more personalized, efficient, and user-friendly solution for anyone who wants to consume PDF content through voice. &lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features of PDF Voice Reader
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Native Text-to-Speech (TTS) Integration
&lt;/h3&gt;

&lt;p&gt;PDF Voice Reader leverages the built-in Text-to-Speech engine of the mobile operating system. This ensures seamless performance without external dependencies. The app converts the text extracted from a PDF into high-quality speech, using the same voice and settings already available on the Android device. Users can also customize the voice directly from their device’s system settings. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. File Selection with Native File Picker
&lt;/h3&gt;

&lt;p&gt;Users can easily select a PDF file from their device using the native Android file picker dialog. Once chosen, the selected document is displayed in the app, ready to be read aloud. This makes the process quick, intuitive, and consistent with the device’s user experience. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa86cwh8gvotypddtq2jo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa86cwh8gvotypddtq2jo.png" alt=" " width="417" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusphgr2urrlubk2fuqdc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusphgr2urrlubk2fuqdc.jpg" alt=" " width="406" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Playback Controls
&lt;/h3&gt;

&lt;p&gt;The app includes simple but powerful controls for listening: &lt;/p&gt;

&lt;p&gt;Play/Pause/Resume the reading at any time. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnv3ukrih4tokojbqr3t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnv3ukrih4tokojbqr3t.png" alt=" " width="800" height="88"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Adjust the reading speed through a dropdown menu with options for slower or faster playback. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8zy3843r4bb8kge1wg6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8zy3843r4bb8kge1wg6.png" alt=" " width="493" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Page Navigation
&lt;/h3&gt;

&lt;p&gt;Reading doesn’t have to start at the beginning of a document. Users can: &lt;/p&gt;

&lt;p&gt;Enter a specific page number to jump directly to that section. &lt;/p&gt;

&lt;p&gt;Restart playback from the chosen page once the controls are activated. &lt;br&gt;
This feature is especially useful for textbooks, research papers, or long-form PDFs. &lt;/p&gt;

&lt;p&gt;Use Next Page and Previous Page buttons to skip directly to different sections. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyy4f6ukqjz2xpj281rvs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyy4f6ukqjz2xpj281rvs.png" alt=" " width="637" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Phrase Ignoring for Cleaner Listening
&lt;/h3&gt;

&lt;p&gt;One of the most unique features of PDF Voice Reader is the ability to ignore repetitive phrases such as headers, footers, or page numbers. &lt;/p&gt;

&lt;p&gt;Users can add these phrases to an “Ignore List” so they won’t be read aloud. &lt;/p&gt;

&lt;p&gt;Each ignored phrase is displayed in a list with a delete icon, allowing users to manage or remove phrases at any time. &lt;br&gt;
This customization significantly improves the listening experience, making the content flow more naturally. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7jp7nxtmvk1h9xk6uf9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7jp7nxtmvk1h9xk6uf9.png" alt=" " width="415" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Android Theme and Controls
&lt;/h3&gt;

&lt;p&gt;The PDF Voice Reader app is built using Android native controls, ensuring a familiar look, feel, and behavior consistent with other Android apps. This not only enhances user-friendliness but also makes the interface more intuitive, as users can rely on the interactions they already know. Additionally, the app automatically adapts to the system’s chosen theme—whether light mode or dark mode—providing a seamless and visually consistent experience. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ef83goi6xsnt54gajjk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ef83goi6xsnt54gajjk.jpg" alt=" " width="230" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Android Studio for Building PDF Voice Reader ?
&lt;/h2&gt;

&lt;p&gt;To create the PDF Voice Reader Android app from scratch, I chose Android Studio as the development environment. Android Studio is the official IDE (Integrated Development Environment) for Android app development, designed specifically for building, testing, and deploying apps on Android devices. Its tight integration with the native Android SDK makes it the most reliable and future-proof choice for native development. &lt;/p&gt;

&lt;h3&gt;
  
  
  1. Access to Native SDKs
&lt;/h3&gt;

&lt;p&gt;Android Studio comes bundled with the latest and previous versions of the Android SDK, ensuring compatibility across a wide range of Android versions. This is critical for building apps that not only use the newest platform features but also remain accessible to users on slightly older devices. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. Built-In Device Simulators
&lt;/h3&gt;

&lt;p&gt;One of Android Studio's most powerful features is its built-in Android Simulator, which allows developers to test the app on multiple device models and Android versions without needing the physical hardware. This makes it possible to verify performance, behavior, and UI responsiveness across a wide variety of scenarios, saving significant development time. &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Standardized Layouts and Controls
&lt;/h3&gt;

&lt;p&gt;Android Studio also provides native UI components and layout tools that strictly follow Android Platform's Interface Guidelines. By leveraging these, the PDF Voice Reader app automatically inherits key Android features such as theming (light and dark modes), accessibility standards, and a familiar look-and-feel. This ensures the app feels natural to users while maintaining high compatibility with Android design principles. &lt;/p&gt;

&lt;h3&gt;
  
  
  4. Streamlined Development Workflow
&lt;/h3&gt;

&lt;p&gt;From code editing and debugging to interface design and deployment, Android Studio offers a comprehensive workflow in one place. This integration reduces complexity and allows for faster, more efficient development compared to using third-party tools. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Kotlin for PDF Voice Reader?
&lt;/h2&gt;

&lt;p&gt;For developing the PDF Voice Reader app, I chose Kotlin as the programming language. Kotlin is Google’s modern, powerful, and intuitive language designed specifically for building apps across the Android ecosystem, including phones, tablets, wear devices and so on. &lt;/p&gt;

&lt;h3&gt;
  
  
  1. Native Performance and Compatibility
&lt;/h3&gt;

&lt;p&gt;Kotlin is fully integrated with the Android SDK and development tools, making it the best choice for achieving native performance. Apps written in Kotlin run efficiently, take advantage of the latest Android features, and integrate seamlessly with system services like Text-to-Speech. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. Simplicity and Readability
&lt;/h3&gt;

&lt;p&gt;Kotlin’s syntax is clean, concise, and expressive, making it easier to write and maintain code compared to older languages like Java. This simplicity helps speed up development while reducing the chances of errors, making the codebase more maintainable over time. &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Safety and Reliability
&lt;/h3&gt;

&lt;p&gt;One of Kotlin’s strengths is its focus on safety. Features like strong typing, optionals, and automatic memory management help catch errors early during compilation rather than at runtime. This leads to more reliable and stable apps—crucial for providing a smooth reading experience to users. &lt;/p&gt;

&lt;h3&gt;
  
  
  4. Modern Features for Faster Development
&lt;/h3&gt;

&lt;p&gt;Kotlin offers powerful features such as closures, generics, and structured concurrency, which make coding more efficient and expressive. These modern tools enable developers to implement features like customizable playback or phrase filtering with less code and greater clarity. &lt;/p&gt;

&lt;h3&gt;
  
  
  5. Future-Proof and Actively Supported
&lt;/h3&gt;

&lt;p&gt;Kotlin is actively maintained and improved by Google and the open-source community. Choosing Kotlin ensures the app will remain compatible with future versions of Android and benefit from ongoing performance improvements, security updates, and new language features. &lt;/p&gt;

&lt;h2&gt;
  
  
  Steps to Create the Project in Android Studio
&lt;/h2&gt;

&lt;p&gt;Since this is a single-screen app, we can start with the Empty Activity app template in Android Studio. Follow these steps: &lt;/p&gt;

&lt;p&gt;Open Android Studio&lt;br&gt;
From File menu, select New , select New Project, select Phone and Tablet  section, select Empty Activity, click Next&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgen8pwbknw4p8tc8xk79.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgen8pwbknw4p8tc8xk79.PNG" alt=" " width="705" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Configure Project with following Settings &lt;/p&gt;

&lt;p&gt;Name: PDFReadAloud &lt;/p&gt;

&lt;p&gt;Package Name: com.productivity &lt;/p&gt;

&lt;p&gt;Language: Kotlin &lt;/p&gt;

&lt;p&gt;Minimum SDK: API 33(can chose the SDK version of your choice)&lt;/p&gt;

&lt;p&gt;Build Configuration Language: Kotlin  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flfryghrah5i40lxgixz7.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flfryghrah5i40lxgixz7.PNG" alt=" " width="707" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Finish to generate the project. &lt;/p&gt;

&lt;p&gt;At this point, Android Studio will scaffold the project with the necessary files and structure, and you’ll be ready to start coding the app. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49xyisrkjh3w9e7xi2bb.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49xyisrkjh3w9e7xi2bb.PNG" alt=" " width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Significant Code Fragments
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Code to open a PDF file selection dialog and display file name.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Button(
                        onClick = {
                            pdfPickerLauncher.launch(arrayOf("application/pdf"))
                        },
                        modifier = Modifier.fillMaxWidth()
                    ) {
                        Icon(
                            imageVector = Icons.Default.FileOpen,
                            contentDescription = "Select PDF File"
                        )
                        Spacer(modifier = Modifier.width(8.dp))
                        Text("Select PDF File")
                    }

                    selectedPdfName?.let {
                        Text(
                            text = "Selected file: $it",
                            style = MaterialTheme.typography.bodyMedium
                        )
                    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    fun openPdfFile(uri: Uri): Boolean {
        try {
            closeCurrentDocument()

            val inputStream = context.contentResolver.openInputStream(uri)
            pdfDocument = PDDocument.load(inputStream)
            totalPages = pdfDocument?.numberOfPages ?: 0
            currentPageNumber = 1

            if (totalPages &amp;gt; 0) {
                _state.value = ReaderState.Loaded(currentPageNumber, totalPages)
                return true
            } else {
                _state.value = ReaderState.Error("No pages found in PDF")
                return false
            }
        } catch (e: Exception) {
            _state.value = ReaderState.Error("Failed to open PDF: ${e.message}")
            return false
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Code to select a specific page number to begin reading from.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Text(
                        text = "Page Controls",
                        style = MaterialTheme.typography.titleMedium
                    )

                    Row(
                        verticalAlignment = Alignment.CenterVertically,
                        modifier = Modifier.fillMaxWidth()
                    ) {
                        OutlinedTextField(
                            value = pageNumber,
                            onValueChange = { value -&amp;gt;
                                if (value.isEmpty() || value.all { it.isDigit() }) {
                                    pageNumber = value
                                }
                            },
                            label = { Text("Page #") },
                            keyboardOptions = KeyboardOptions(
                                keyboardType = KeyboardType.Number,
                                imeAction = ImeAction.Done
                            ),
                            keyboardActions = KeyboardActions(
                                onDone = { keyboardController?.hide() }
                            ),
                            modifier = Modifier.weight(1f)
                        )

                        when (readerState) {
                            is PdfReaderService.ReaderState.Loaded,
                            is PdfReaderService.ReaderState.Paused -&amp;gt; {
                                Text(
                                    text = "of ${(readerState as? PdfReaderService.ReaderState.Loaded)?.totalPages
                                        ?: (readerState as? PdfReaderService.ReaderState.Paused)?.totalPages
                                        ?: (readerState as? PdfReaderService.ReaderState.Reading)?.totalPages
                                        ?: 0}",
                                    modifier = Modifier
                                        .padding(horizontal = 8.dp)
                                        .align(Alignment.CenterVertically)
                                )
                            }
                            else -&amp;gt; {
                                Text(
                                    text = "of ${(readerState as? PdfReaderService.ReaderState.Loaded)?.totalPages
                                        ?: (readerState as? PdfReaderService.ReaderState.Paused)?.totalPages
                                        ?: (readerState as? PdfReaderService.ReaderState.Reading)?.totalPages
                                        ?: 0}",
                                    modifier = Modifier
                                        .padding(horizontal = 8.dp)
                                        .align(Alignment.CenterVertically)
                                )

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    fun nextPage() {
        if (currentPageNumber &amp;lt; totalPages) {
            readPage(currentPageNumber + 1)
        }
    }

    fun previousPage() {
        if (currentPageNumber &amp;gt; 1) {
            readPage(currentPageNumber - 1)
        }
    }

    fun readPage(pageNumber: Int = currentPageNumber) {
        if (!isInitialized || pdfDocument == null) {
            _state.value = ReaderState.Error("Reader not initialized or no PDF loaded")
            return
        }

        if (pageNumber &amp;lt; 1 || pageNumber &amp;gt; totalPages) {
            _state.value = ReaderState.Error("Invalid page number")
            return
        }

        try {
            currentPageNumber = pageNumber
            val stripper = PDFTextStripper()
            stripper.startPage = pageNumber
            stripper.endPage = pageNumber

            currentText = stripper.getText(pdfDocument).lowercase()

            // Apply excluded text filtering
            var textToRead = currentText
            excludedTexts.forEach { excludedText -&amp;gt;
                if (excludedText.isNotBlank()) {
                    textToRead = textToRead.replace(excludedText.lowercase(), "")
                }
            }

            if (textToRead.isBlank()) {
                _state.value = ReaderState.Reading(currentPageNumber, totalPages)
                if (continuousReading &amp;amp;&amp;amp; currentPageNumber &amp;lt; totalPages) {
                    // If page is blank and continuous reading is enabled, skip to next page
                    readPage(currentPageNumber + 1)
                } else {
                }
                return
            }

            _state.value = ReaderState.Reading(currentPageNumber, totalPages)
            stopReading()
            textToSpeech?.speak(textToRead, TextToSpeech.QUEUE_FLUSH, null, "pdf_page_$pageNumber")
        } catch (e: Exception) {
            _state.value = ReaderState.Error("Failed to read page: ${e.message}")
        }
    }
    fun goToPage(pageNumber: Int) {
        val validPageNumber = min(max(1, pageNumber), totalPages)
        readPage(validPageNumber)
    }

    fun setContinuousReading(enabled: Boolean) {
        continuousReading = enabled
    }

    fun stopReading() {
        textToSpeech?.stop()
        if (_state.value is ReaderState.Reading) {
            _state.value = ReaderState.Paused(currentPageNumber, totalPages)
        }
    }

    fun closeCurrentDocument() {
        stopReading()
        pdfDocument?.close()
        pdfDocument = null
        currentText = ""
        totalPages = 0
        _state.value = ReaderState.Idle
    }

    fun shutdown() {
        stopReading()
        textToSpeech?.shutdown()
        textToSpeech = null
        closeCurrentDocument()
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Code to change the reading speed, either faster or slower.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                        OutlinedButton(
                            onClick = { isSpeedMenuExpanded = true }
                        ) {
                            Text("Speed: ${selectedSpeed}x")
                        }

                        DropdownMenu(
                            expanded = isSpeedMenuExpanded,
                            onDismissRequest = { isSpeedMenuExpanded = false },
                            modifier = Modifier.wrapContentSize()
                        ) {
                            speedOptions.forEach { speed -&amp;gt;
                                DropdownMenuItem(
                                    onClick = {
                                        selectedSpeed = speed
                                        pdfReaderService.setReadingSpeed(speed)
                                        isSpeedMenuExpanded = false
                                    },
                                    text = { Text("${speed}x") }
                                )
                            }
                        }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    fun setReadingSpeed(speedFactor: Float) {
        textToSpeech?.setSpeechRate(speedFactor)
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Code to pause, stop, or restart the reading.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                        // Previous Page Button
                        val isPreviousEnabled = when (readerState) {
                            is PdfReaderService.ReaderState.Loaded -&amp;gt; (readerState as PdfReaderService.ReaderState.Loaded).currentPage &amp;gt; 1
                            is PdfReaderService.ReaderState.Reading -&amp;gt; (readerState as PdfReaderService.ReaderState.Reading).currentPage &amp;gt; 1
                            is PdfReaderService.ReaderState.Paused -&amp;gt; (readerState as PdfReaderService.ReaderState.Paused).currentPage &amp;gt; 1
                            else -&amp;gt; false
                        }

                        Button(
                            onClick = { pdfReaderService.previousPage() },
                            enabled = isPreviousEnabled,
                            modifier = Modifier.weight(1f)
                        ) {
                            Text("Prev Page")
                        }

                        Spacer(modifier = Modifier.width(4.dp))

                        // Next Page Button
                        val isNextEnabled = when (readerState) {
                            is PdfReaderService.ReaderState.Loaded -&amp;gt; {
                                (readerState as PdfReaderService.ReaderState.Loaded).currentPage &amp;lt; (readerState as PdfReaderService.ReaderState.Loaded).totalPages
                            }
                            is PdfReaderService.ReaderState.Reading -&amp;gt; {
                                (readerState as PdfReaderService.ReaderState.Reading).currentPage &amp;lt; (readerState as PdfReaderService.ReaderState.Reading).totalPages
                            }
                            is PdfReaderService.ReaderState.Paused -&amp;gt; {
                                (readerState as PdfReaderService.ReaderState.Paused).currentPage &amp;lt; (readerState as PdfReaderService.ReaderState.Paused).totalPages
                            }
                            else -&amp;gt; false
                        }

                        Button(
                            onClick = { pdfReaderService.nextPage() },
                            enabled = isNextEnabled,
                            modifier = Modifier.weight(1f)
                        ) {
                            Text("Next Page")
                            //Spacer(modifier = Modifier.width(4.dp))
                        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    fun pauseReading() {
        if (textToSpeech?.isSpeaking == true) {
            textToSpeech?.stop()
            _state.value = ReaderState.Paused(currentPageNumber, totalPages)
        }
    }

    fun resumeReading() {
        if (_state.value is ReaderState.Paused) {
            val textToRead = currentText
            textToSpeech?.speak(textToRead, TextToSpeech.QUEUE_FLUSH, null, "pdf_resume_$currentPageNumber")
            _state.value = ReaderState.Reading(currentPageNumber, totalPages)
        }
    }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Code to add phrases to an exclusion list and remove them individually when needed.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Text(
                        text = "Exclude Text",
                        style = MaterialTheme.typography.titleMedium
                    )

                    Row(
                        verticalAlignment = Alignment.CenterVertically,
                        modifier = Modifier.fillMaxWidth()
                    ) {
                        OutlinedTextField(
                            value = excludeText,
                            onValueChange = { excludeText = it },
                            label = { Text("Text to exclude") },
                            keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
                            keyboardActions = KeyboardActions(
                                onDone = {
                                    if (excludeText.isNotBlank()) {
                                        coroutineScope.launch {
                                            preferenceRepository.addExcludedText(excludeText)
                                            excludeText = ""
                                        }
                                    }
                                    keyboardController?.hide()
                                }
                            ),
                            modifier = Modifier.weight(1f)
                        )

                        Spacer(modifier = Modifier.width(8.dp))

                        IconButton(
                            onClick = {
                                if (excludeText.isNotBlank()) {
                                    coroutineScope.launch {
                                        preferenceRepository.addExcludedText(excludeText)
                                        excludeText = ""
                                    }
                                }
                            }
                        ) {
                            Icon(
                                imageVector = Icons.Default.Add,
                                contentDescription = "Add excluded text"
                            )
                        }
                    }

                    Spacer(modifier = Modifier.height(8.dp))

                    if (excludedTexts.isNotEmpty()) {
                        Divider()
                        Spacer(modifier = Modifier.height(8.dp))

                        Text(
                            text = "Excluded Texts:",
                            style = MaterialTheme.typography.bodyMedium
                        )

                        FlowRow(
                            horizontalArrangement = Arrangement.spacedBy(8.dp),
                            verticalArrangement = Arrangement.spacedBy(8.dp),
                            modifier = Modifier.fillMaxWidth()
                        ) {
                            excludedTexts.forEach { text -&amp;gt;
                                ExcludedTextChip(
                                    text = text,
                                    onRemove = {
                                        coroutineScope.launch {
                                            preferenceRepository.removeExcludedText(text)
                                        }
                                    }
                                )
                            }
                        }
                    }
                }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    suspend fun addExcludedText(text: String) {
        context.dataStore.edit { preferences -&amp;gt;
            val currentList = preferences[EXCLUDED_TEXT_KEY]?.split(",") ?: emptyList()
            if (text.isNotBlank() &amp;amp;&amp;amp; !currentList.contains(text)) {
                val newList = currentList.toMutableList().apply { add(text) }
                preferences[EXCLUDED_TEXT_KEY] = newList.joinToString(",")
            }
        }
    }

    suspend fun removeExcludedText(text: String) {
        context.dataStore.edit { preferences -&amp;gt;
            val currentList = preferences[EXCLUDED_TEXT_KEY]?.split(",") ?: emptyList()
            val newList = currentList.filter { it != text }
            preferences[EXCLUDED_TEXT_KEY] = newList.joinToString(",")
        }
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This project's full code is in GitHub @ &lt;a href="https://github.com/shravyanayani/AndroidPdfVoiceReader" rel="noopener noreferrer"&gt;https://github.com/shravyanayani/AndroidPdfVoiceReader&lt;/a&gt;&lt;/strong&gt; &lt;/p&gt;




</description>
    </item>
    <item>
      <title>Develop a native iOS app : PDF Voice Reader</title>
      <dc:creator>Srav Nayani</dc:creator>
      <pubDate>Sat, 27 Sep 2025 16:27:52 +0000</pubDate>
      <link>https://forem.com/sravnay/develop-a-native-ios-app-pdf-voice-reader-353h</link>
      <guid>https://forem.com/sravnay/develop-a-native-ios-app-pdf-voice-reader-353h</guid>
      <description>&lt;p&gt;This project's full code is in GitHub @ &lt;a href="https://github.com/shravyanayani/iosPdfVoiceReader" rel="noopener noreferrer"&gt;https://github.com/shravyanayani/iosPdfVoiceReader&lt;/a&gt; &lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzp3860xo97butiue031n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzp3860xo97butiue031n.png" alt=" " width="394" height="591"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Choose Native Apps?
&lt;/h2&gt;

&lt;p&gt;Native apps on iOS mobile devices are designed to take full advantage of the underlying platform’s features and capabilities. In this case, the Text-to-Speech (TTS) functionality of the phone can be seamlessly integrated, allowing the app to read PDF content aloud without limitations or external dependencies. &lt;/p&gt;

&lt;p&gt;Beyond feature access, iOS native apps also excel in performance and responsiveness. Since they are optimized for the specific operating system, they can handle tasks like parsing text and generating speech more efficiently, resulting in a smoother, faster, and more reliable user experience. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why Create PDF Voice Reader?
&lt;/h2&gt;

&lt;p&gt;PDF is one of the most widely used document formats, valued for its portability across devices and operating systems. Books, research papers, articles, and even web pages or documents can easily be saved as PDFs, making them a universal standard for digital reading. &lt;/p&gt;

&lt;p&gt;While PDFs are convenient for distribution, reading them visually isn’t always practical or desirable. In many situations—such as while driving, before sleep, exercising, or when reducing screen time—having the document read aloud can be far more convenient. &lt;/p&gt;

&lt;p&gt;Although there are existing apps in app stores that provide PDF-to-speech functionality, many of them come with drawbacks. They often include intrusive advertisements and lack the customization options users truly need. For example, most do not allow skipping repetitive elements like headers, footers, or page numbers, which disrupt the listening experience. &lt;/p&gt;

&lt;p&gt;By creating PDF Voice Reader, these limitations can be overcome. The app not only eliminates ads but also offers greater flexibility, allowing users to tailor the reading experience to their needs. This makes it a more personalized, efficient, and user-friendly solution for anyone who wants to consume PDF content through voice. &lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features of PDF Voice Reader
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Native Text-to-Speech (TTS) Integration
&lt;/h3&gt;

&lt;p&gt;PDF Voice Reader leverages the built-in Text-to-Speech engine of the mobile operating system. This ensures seamless performance without external dependencies. The app converts the text extracted from a PDF into high-quality speech, using the same voice and settings already available on the iOS device. Users can also customize the voice directly from their device’s system settings. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. File Selection with Native File Picker
&lt;/h3&gt;

&lt;p&gt;Users can easily select a PDF file from their device using the native iOS file picker dialog. Once chosen, the selected document is displayed in the app, ready to be read aloud. This makes the process quick, intuitive, and consistent with the device’s user experience. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxib3906ud4h777kww7l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxib3906ud4h777kww7l.png" alt=" " width="248" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Playback Controls
&lt;/h3&gt;

&lt;p&gt;The app includes simple but powerful controls for listening: &lt;/p&gt;

&lt;p&gt;Play/Pause/Resume the reading at any time. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7dilh24c6x8a4zdq3b0f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7dilh24c6x8a4zdq3b0f.png" alt=" " width="455" height="76"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Adjust the reading speed through a dropdown menu with options for slower or faster playback. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpfirs7cwxdoebfs90e78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpfirs7cwxdoebfs90e78.png" alt=" " width="331" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Page Navigation
&lt;/h3&gt;

&lt;p&gt;Reading doesn’t have to start at the beginning of a document. Users can: &lt;/p&gt;

&lt;p&gt;Enter a specific page number to jump directly to that section. &lt;/p&gt;

&lt;p&gt;Restart playback from the chosen page once the controls are activated. &lt;br&gt;
This feature is especially useful for textbooks, research papers, or long-form PDFs. &lt;/p&gt;

&lt;p&gt;Use Next Page and Previous Page buttons to skip directly to different sections. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu53gp51ubc6t6rgnxowk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu53gp51ubc6t6rgnxowk.png" alt=" " width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Phrase Ignoring for Cleaner Listening
&lt;/h3&gt;

&lt;p&gt;One of the most unique features of PDF Voice Reader is the ability to ignore repetitive phrases such as headers, footers, or page numbers. &lt;/p&gt;

&lt;p&gt;Users can add these phrases to an “Ignore List” so they won’t be read aloud. &lt;/p&gt;

&lt;p&gt;Each ignored phrase is displayed in a list with a delete icon, allowing users to manage or remove phrases at any time. &lt;br&gt;
This customization significantly improves the listening experience, making the content flow more naturally. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca00s49g017gabukam6m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca00s49g017gabukam6m.png" alt=" " width="775" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6. iOS Theme and Controls
&lt;/h3&gt;

&lt;p&gt;The PDF Voice Reader app is built using iOS native controls, ensuring a familiar look, feel, and behavior consistent with other iOS apps. This not only enhances user-friendliness but also makes the interface more intuitive, as users can rely on the interactions they already know. Additionally, the app automatically adapts to the system’s chosen theme—whether light mode or dark mode—providing a seamless and visually consistent experience. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv9830i15ha0ky4h2e9w3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv9830i15ha0ky4h2e9w3.png" alt=" " width="236" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Xcode for Building PDF Voice Reader ?
&lt;/h2&gt;

&lt;p&gt;To create the PDF Voice Reader iOS app from scratch, I chose Xcode as the development environment. Xcode is the official IDE (Integrated Development Environment) for iOS, designed specifically for building, testing, and deploying apps on Apple devices. Its tight integration with the native iOS SDK makes it the most reliable and future-proof choice for native development. &lt;/p&gt;

&lt;h3&gt;
  
  
  1. Access to Native SDKs
&lt;/h3&gt;

&lt;p&gt;Xcode comes bundled with the latest and previous versions of the iOS SDK, ensuring compatibility across a wide range of iOS versions. This is critical for building apps that not only use the newest platform features but also remain accessible to users on slightly older devices. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. Built-In Device Simulators
&lt;/h3&gt;

&lt;p&gt;One of Xcode’s most powerful features is its built-in iOS Simulator, which allows developers to test the app on multiple device models and iOS versions without needing the physical hardware. This makes it possible to verify performance, behavior, and UI responsiveness across a wide variety of scenarios, saving significant development time. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjf2cyilw4lr0aholowpb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjf2cyilw4lr0aholowpb.png" alt=" " width="192" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Standardized Layouts and Controls
&lt;/h3&gt;

&lt;p&gt;Xcode also provides native UI components and layout tools that strictly follow Apple’s Human Interface Guidelines. By leveraging these, the PDF Voice Reader app automatically inherits key iOS features such as theming (light and dark modes), accessibility standards, and a familiar look-and-feel. This ensures the app feels natural to users while maintaining high compatibility with iOS design principles. &lt;/p&gt;

&lt;h3&gt;
  
  
  4. Streamlined Development Workflow
&lt;/h3&gt;

&lt;p&gt;From code editing and debugging to interface design and deployment, Xcode offers a comprehensive workflow in one place. This integration reduces complexity and allows for faster, more efficient development compared to using third-party tools. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Swift for PDF Voice Reader?
&lt;/h2&gt;

&lt;p&gt;For developing the PDF Voice Reader app, I chose Swift as the programming language. Swift is Apple’s modern, powerful, and intuitive language designed specifically for building apps across the Apple ecosystem, including iOS, iPadOS, watchOS, and macOS. &lt;/p&gt;

&lt;h3&gt;
  
  
  1. Native Performance and Compatibility
&lt;/h3&gt;

&lt;p&gt;Swift is fully integrated with the iOS SDK and Apple’s development tools, making it the best choice for achieving native performance. Apps written in Swift run efficiently, take advantage of the latest iOS features, and integrate seamlessly with system services like Text-to-Speech. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. Simplicity and Readability
&lt;/h3&gt;

&lt;p&gt;Swift’s syntax is clean, concise, and expressive, making it easier to write and maintain code compared to older languages like Objective-C. This simplicity helps speed up development while reducing the chances of errors, making the codebase more maintainable over time. &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Safety and Reliability
&lt;/h3&gt;

&lt;p&gt;One of Swift’s strengths is its focus on safety. Features like strong typing, optionals, and automatic memory management help catch errors early during compilation rather than at runtime. This leads to more reliable and stable apps—crucial for providing a smooth reading experience to users. &lt;/p&gt;

&lt;h3&gt;
  
  
  4. Modern Features for Faster Development
&lt;/h3&gt;

&lt;p&gt;Swift offers powerful features such as closures, generics, and structured concurrency, which make coding more efficient and expressive. These modern tools enable developers to implement features like customizable playback or phrase filtering with less code and greater clarity. &lt;/p&gt;

&lt;h3&gt;
  
  
  5. Future-Proof and Actively Supported
&lt;/h3&gt;

&lt;p&gt;Swift is actively maintained and improved by Apple and the open-source community. Choosing Swift ensures the app will remain compatible with future versions of iOS and benefit from ongoing performance improvements, security updates, and new language features. &lt;/p&gt;

&lt;h2&gt;
  
  
  Steps to Create the Project in Xcode
&lt;/h2&gt;

&lt;p&gt;Since this is a single-screen app, we can start with the standard iOS app template in Xcode. Follow these steps: &lt;/p&gt;

&lt;p&gt;Open Xcode &lt;/p&gt;

&lt;p&gt;From the top menu, go to: &lt;br&gt;
File → New → Project &lt;/p&gt;

&lt;p&gt;Choose Template &lt;/p&gt;

&lt;p&gt;In the dialog that appears, select the iOS tab. &lt;/p&gt;

&lt;p&gt;Under Application, choose App. &lt;/p&gt;

&lt;p&gt;Click Next. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmdnq5z62fztgj3rvzcv4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmdnq5z62fztgj3rvzcv4.png" alt=" " width="719" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Configure Project with following Settings &lt;/p&gt;

&lt;p&gt;Product Name: PDFReadAloud &lt;/p&gt;

&lt;p&gt;Organization Identifier: com.productivity &lt;/p&gt;

&lt;p&gt;Interface: SwiftUI &lt;/p&gt;

&lt;p&gt;Language: Swift &lt;/p&gt;

&lt;p&gt;Check the box for Include Tests to add a testing target. &lt;/p&gt;

&lt;p&gt;Click Next. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjze0qwgusc2j3faucad7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjze0qwgusc2j3faucad7.png" alt=" " width="718" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select Project Location &lt;/p&gt;

&lt;p&gt;Create or select a folder named PDFReadAloud. &lt;/p&gt;

&lt;p&gt;Click Create to generate the project. &lt;/p&gt;

&lt;p&gt;At this point, Xcode will scaffold the project with the necessary files and structure, and you’ll be ready to start coding the app. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd1exi7it9sj3kda0aeuc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd1exi7it9sj3kda0aeuc.png" alt=" " width="800" height="515"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Significant Code Fragments
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Code to open a PDF file selection dialog and display file name.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            Button(action: {
                pdfViewModel.showDocumentPicker = true
            }) {
                HStack {
                    Image(systemName: "doc.fill")
                    Text("Select PDF File")
                }
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(8)
            }
            .sheet(isPresented: $pdfViewModel.showDocumentPicker) {
                DocumentPicker(pdfURL: $pdfViewModel.pdfURL, pdfFileName: $pdfViewModel.pdfFileName)
            }

            if !pdfViewModel.pdfFileName.isEmpty {
                Text("Selected file: \(pdfViewModel.pdfFileName)")
                    .font(.subheadline)
            }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct DocumentPicker: UIViewControllerRepresentable {
    @Binding var pdfURL: URL?
    @Binding var pdfFileName: String
    @Environment(\.presentationMode) var presentationMode

    func makeUIViewController(context: Context) -&amp;gt; UIDocumentPickerViewController {
        let picker = UIDocumentPickerViewController(forOpeningContentTypes: [UTType.pdf])
        picker.allowsMultipleSelection = false
        picker.delegate = context.coordinator

        // Request access to the document
        picker.shouldShowFileExtensions = true

        return picker
    }

    func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {}

    func makeCoordinator() -&amp;gt; Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, UIDocumentPickerDelegate {
        let parent: DocumentPicker

        init(_ parent: DocumentPicker) {
            self.parent = parent
        }

        func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
            guard let url = urls.first else { return }

            // Start accessing the security-scoped resource
            let didStartAccessing = url.startAccessingSecurityScopedResource()

            defer {
                if didStartAccessing {
                    url.stopAccessingSecurityScopedResource()
                }
            }

            do {
                // Create a copy in the app's documents directory for persistent access
                let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
                let destinationURL = documentsDirectory.appendingPathComponent(url.lastPathComponent)

                // Remove any existing file
                if FileManager.default.fileExists(atPath: destinationURL.path) {
                    try FileManager.default.removeItem(at: destinationURL)
                }

                // Copy the file
                try FileManager.default.copyItem(at: url, to: destinationURL)

                // Update the view model with the local URL
                DispatchQueue.main.async {
                    self.parent.pdfURL = destinationURL
                    self.parent.pdfFileName = url.lastPathComponent
                }
            } catch {
                print("Error copying file: \(error.localizedDescription)")

                // If copying fails, try to use the original URL directly
                DispatchQueue.main.async {
                    // Create a bookmark for persistent access
                    do {
                        let bookmarkData = try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil)
                        UserDefaults.standard.set(bookmarkData, forKey: "pdfBookmark")

                        self.parent.pdfURL = url
                        self.parent.pdfFileName = url.lastPathComponent
                    } catch {
                        print("Failed to create bookmark: \(error.localizedDescription)")
                    }
                }
            }

            parent.presentationMode.wrappedValue.dismiss()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Code to select a specific page number to begin reading from.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Text("Page #")
                TextField("1", text: $pdfViewModel.pageNumberText)
                    .keyboardType(.numberPad)
                    .frame(width: 60)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .onChange(of: pdfViewModel.pageNumberText) { newValue in
                        if let pageNumber = Int(newValue), pageNumber &amp;gt; 0 {
                            pdfViewModel.currentPage = pageNumber - 1
                        }
                    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Button(action: {
                    pdfViewModel.previousPage()
                }) {
                    HStack {
                        Image(systemName: "arrow.backward")
                        Text("Previous Page")
                    }
                    .padding()
                    .background(pdfViewModel.hasPreviousPage ? Color.blue : Color.gray)
                    .foregroundColor(.white)
                    .cornerRadius(8)
                }
                .disabled(!pdfViewModel.hasPreviousPage)

                Button(action: {
                    pdfViewModel.nextPage()
                }) {
                    HStack {
                        Image(systemName: "arrow.forward")
                        Text("Next Page")
                    }
                    .padding()
                    .background(pdfViewModel.hasNextPage ? Color.blue : Color.gray)
                    .foregroundColor(.white)
                    .cornerRadius(8)
                }
                .disabled(!pdfViewModel.hasNextPage)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    func nextPage() {
        guard hasNextPage else { 
            updateStatus("Already at the last page", isError: true)
            return 
        }

        currentPage += 1
        pageNumberText = "\(currentPage + 1)"
        readCurrentPage()
    }

    func previousPage() {
        guard hasPreviousPage else { 
            updateStatus("Already at the first page", isError: true)
            return 
        }

        currentPage -= 1
        pageNumberText = "\(currentPage + 1)"
        readCurrentPage()
    }

    var hasPreviousPage: Bool {
        guard let pdfDocument = pdfDocument else { return false }
        return currentPage &amp;gt; 0
    }

    var hasNextPage: Bool {
        guard let pdfDocument = pdfDocument else { return false }
        return currentPage &amp;lt; pdfDocument.pageCount - 1
    }

    func readPDF() {
        guard let url = pdfURL else { 
            updateStatus("No PDF file selected", isError: true)
            return 
        }

        // Check if we need to restore security-scoped resource access
        var didStartAccessing = false
        if !url.isFileURL || !FileManager.default.fileExists(atPath: url.path) {
            // Try to resolve from bookmark if needed
            if let bookmarkData = UserDefaults.standard.data(forKey: "pdfBookmark") {
                do {
                    var isStale = false
                    let resolvedURL = try URL(resolvingBookmarkData: bookmarkData, 
                                             options: .withoutUI, 
                                             relativeTo: nil, 
                                             bookmarkDataIsStale: &amp;amp;isStale)

                    if isStale {
                        updateStatus("PDF bookmark is stale, please select the file again", isError: true)
                        return
                    }

                    // Start accessing the security-scoped resource
                    didStartAccessing = resolvedURL.startAccessingSecurityScopedResource()

                    // Update the URL to the resolved one
                    pdfURL = resolvedURL
                } catch {
                    updateStatus("Failed to access PDF file: \(error.localizedDescription)", isError: true)
                    return
                }
            }
        }

        // Create PDF document with proper security options
        let pdfDoc = PDFDocument(url: url)

        // Stop accessing the security-scoped resource if needed
        if didStartAccessing {
            url.stopAccessingSecurityScopedResource()
        }

        if pdfDoc == nil {
            updateStatus("Failed to load PDF document. The file may be corrupted or password-protected.", isError: true)
            return
        }

        self.pdfDocument = pdfDoc

        if let pageNumber = Int(pageNumberText), pageNumber &amp;gt; 0 &amp;amp;&amp;amp; pageNumber &amp;lt;= pdfDocument?.pageCount ?? 0 {
            currentPage = pageNumber - 1
        } else {
            currentPage = 0
            pageNumberText = "1"
            updateStatus("Invalid page number. Starting from page 1", isError: true)
        }

        updateStatus("Successfully loaded PDF with \(pdfDocument?.pageCount ?? 0) pages", isError: false)
        readCurrentPage()
    }

    func readCurrentPage() {
        print("-------in readCurrentPage pdfDocument.pageCount \(currentPage)")
        guard let pdfDocument = pdfDocument, currentPage &amp;lt; pdfDocument.pageCount else { 
            updateStatus("Invalid page number or no PDF loaded", isError: true)
            return 
        }

        speechSynthesizer.stopSpeaking(at: .immediate)

        guard let page = pdfDocument.page(at: currentPage) else { 
            updateStatus("Failed to load page \(currentPage + 1)", isError: true)
            return 
        }

        // Extract text from the PDF page
        var pageText = page.string ?? "Empty on purpose"


        // If still empty, show an error
        if pageText.isEmpty {
            print("No text found on page \(currentPage + 1)")
            pageText = "No readable text found on this page."
            updateStatus("No readable text found on page \(currentPage + 1)", isError: true)
        } else {
            updateStatus("Reading page \(currentPage + 1) of \(pdfDocument.pageCount)", isError: false)
        }

        print("-------in readCurrentPage pageText =  \(pageText)")

        // Filter out excluded texts
        for excludedText in excludedTexts {
            pageText = pageText.replacingOccurrences(of: excludedText, with: "")
        }

        // Create and configure the utterance
        let utterance = AVSpeechUtterance(string: pageText)
        utterance.rate = selectedRate * AVSpeechUtteranceDefaultSpeechRate

        utterance.voice = AVSpeechSynthesisVoice(language: "en-US")

        // Set pitch and volume for better speech quality
        utterance.pitchMultiplier = 1.0
        utterance.volume = 1.0

        currentUtterance = utterance
        speechSynthesizer.speak(utterance)
        isPlaying = true
        shouldContinueToNextPage = true

        print("Reading page \(currentPage + 1) with \(pageText.count) characters")
    }

    // Helper function to update status messages
    private func updateStatus(_ message: String, isError: Bool) {
        DispatchQueue.main.async {
            self.statusMessage = message
            self.isError = isError

            // Auto-clear success messages after 5 seconds
            if !isError {
                DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
                    // Only clear if it's still the same message
                    if self.statusMessage == message {
                        self.statusMessage = ""
                    }
                }
            }
        }
    }

    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
        // Automatically move to the next page if we're at the end of the current page
        if shouldContinueToNextPage &amp;amp;&amp;amp; hasNextPage {
            DispatchQueue.main.async {
                self.nextPage()
            }
        } else {
            DispatchQueue.main.async {
                self.isPlaying = false
            }
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Code to change the reading speed, either faster or slower.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Text("Reading Speed")
                Picker("", selection: $pdfViewModel.selectedRate) {
                    ForEach(pdfViewModel.availableRates, id: \.self) { rate in
                        Text("\(rate, specifier: "%.2f")x").tag(rate)
                    }
                }
                .pickerStyle(MenuPickerStyle())
                .onChange(of: pdfViewModel.selectedRate) { newValue in
                    pdfViewModel.updateSpeechRate(newValue)
                }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    var availableRates: [Float] = [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    func updateSpeechRate(_ rate: Float) {
        if let utterance = currentUtterance, speechSynthesizer.isSpeaking {
            speechSynthesizer.stopSpeaking(at: .immediate)

            let newUtterance = AVSpeechUtterance(string: utterance.speechString)
            newUtterance.rate = rate * AVSpeechUtteranceDefaultSpeechRate
            newUtterance.voice = utterance.voice

            currentUtterance = newUtterance
            speechSynthesizer.speak(newUtterance)
            isPlaying = true
        }
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Code to pause, stop, or restart the reading.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Button(action: {
                    pdfViewModel.togglePlayPause()
                }) {
                    HStack {
                        Image(systemName: pdfViewModel.isPlaying ? "pause.fill" : "play.fill")
                        Text(pdfViewModel.isPlaying ? "Pause" : "Play")
                    }
                    .padding()
                    .background(pdfViewModel.pdfURL != nil ? Color.blue : Color.gray)
                    .foregroundColor(.white)
                    .cornerRadius(8)
                }
                .disabled(pdfViewModel.pdfURL == nil)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func togglePlayPause() {
        if isPlaying {
            speechSynthesizer.pauseSpeaking(at: .word)
            isPlaying = false
            shouldContinueToNextPage = false
            updateStatus("Paused reading at page \(currentPage + 1)", isError: false)
        } else {
            if speechSynthesizer.isPaused {
                speechSynthesizer.continueSpeaking()
                isPlaying = true
                shouldContinueToNextPage = true
                updateStatus("Resumed reading from page \(currentPage + 1)", isError: false)
            } else {
                readCurrentPage()
            }
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Code to add phrases to an exclusion list and remove them individually when needed.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Text("Exclude Text:")
                    .font(.headline)

                HStack {
                    TextField("Text to exclude", text: $excludeText)
                        .textFieldStyle(RoundedBorderTextFieldStyle())

                    Button(action: {
                        if !excludeText.isEmpty {
                            pdfViewModel.addExcludeText(excludeText)
                            excludeText = ""
                        }
                    }) {
                        Image(systemName: "plus.circle.fill")
                            .foregroundColor(.blue)
                    }
                }

                ScrollView(.horizontal, showsIndicators: true) {
                    HStack {
                        ForEach(pdfViewModel.excludedTexts, id: \.self) { text in
                            HStack {
                                Text(text)
                                    .padding(.horizontal, 8)
                                    .padding(.vertical, 4)
                                    .background(Color.red.opacity(0.2))
                                    .cornerRadius(4)

                                Button(action: {
                                    pdfViewModel.removeExcludeText(text)
                                }) {
                                    Image(systemName: "xmark.circle.fill")
                                        .foregroundColor(.red)
                                }
                            }
                        }
                    }
                }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    func addExcludeText(_ text: String) {
        if !excludedTexts.contains(text) {
            excludedTexts.append(text)
            saveExcludedTexts()
        }
    }

    func removeExcludeText(_ text: String) {
        if let index = excludedTexts.firstIndex(of: text) {
            excludedTexts.remove(at: index)
            saveExcludedTexts()
        }
    }

    func saveExcludedTexts() {
        UserDefaults.standard.set(excludedTexts, forKey: "excludedTexts")
    }

    func loadExcludedTexts() {
        if let savedTexts = UserDefaults.standard.stringArray(forKey: "excludedTexts") {
            excludedTexts = savedTexts
        }
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;This project's full code is in GitHub @ &lt;a href="https://github.com/shravyanayani/iosPdfVoiceReader" rel="noopener noreferrer"&gt;https://github.com/shravyanayani/iosPdfVoiceReader&lt;/a&gt;&lt;/strong&gt; &lt;/p&gt;




</description>
      <category>softwaredevelopment</category>
      <category>mobile</category>
      <category>ios</category>
      <category>a11y</category>
    </item>
  </channel>
</rss>
