I Was Jobless for 6 Months, So I Built an App That Got Me Hired in a Week

Ahmed ArigbabuAhmed Arigbabu
10 min read

Six months. That's 180 days of staring at the ceiling, refreshing LinkedIn until my thumb went numb, and writing cover letters into the void. My savings were vaporizing, my confidence was shot, and my imposter syndrome had its own seat at the dinner table. I was stuck in the job-seeking hamster wheel, and I was getting dizzy.

One night, scrolling through Twitter (or X as the billionaire would like to call it), I saw a tweet about a "Hidden Job Board" API. It scraped company career pages directly, unearthing jobs that never made it to the big platforms. A lightbulb went off. A dim, flickering, battery-powered lightbulb, but a lightbulb nonetheless.

What if I stopped just looking for a job and started building my way into one? What if I could create a tool that was better, faster, and more ruthless than any other job board? An app built for one purpose: to get me employed, fast.

So I did. I gave myself one week. I locked myself in, fueled by desperation, and built Jobstracker. It’s not just a job search app; it’s an application tracking system, an analytics dashboard, and a testament to what a developer can do when their back is against the wall.

This is the story of how I built it. From the first line of code to the flood of interview requests. Let's get to work.

Part 1: The Spark - Ideation and Setup

Every project starts with a problem. My problem was unemployment. The solution needed to be a system, not just a search tool.

My requirements were simple and brutal:

  1. Find the Hidden Jobs: Integrate the "Hidden Job Board" API to find jobs nobody else sees.

  2. Track Everything: I needed to save jobs, track my application status (Saved, Applied, Interviewing, Offered, Rejected), and see it all in one place. No more messy spreadsheets.

  3. Analyze My Grind: I needed data. How many jobs was I applying to? Where was I getting stuck? I wanted charts that would tell me if I was actually making progress or just spinning my wheels.

  4. No-Nonsense UI: The design had to be functional, high-contrast, and efficient. I wasn't building a startup MVP to impress investors; I was building a weapon for my job hunt. The aesthetic? Let's call it "Brutalist Efficiency."

The Arsenal: Our Tech Stack

  • Framework: Flutter 3.x

  • API: Hidden Job Board on RapidAPI

  • State Management: Provider

  • Networking: Dio

  • Persistence: SharedPreferences

  • Charting: fl_chart

  • Styling: Google Fonts and a custom theme.

Project Setup & Structure

First things first, let's create the project.

$ flutter create job_tracker_app
$ cd job_tracker_app

Now, the project structure. A clean house is a clean mind. I structured the lib folder to be scalable and easy to navigate.

lib
├── api/          # All things networking (our ApiService lives here)
├── core/         # Shared stuff: constants, themes, widgets, utils
├── data/         # Models, providers (the app's brain)
├── features/     # Each screen/feature gets its own folder
├── main.dart     # The entry point
└── services/     # For other services (e.g., push notifications in the future)

Pro Tip: Feature-First Architecture

I organize my folders by feature (job_search, tracker, etc.), not by type (screens, widgets). Why? When I'm working on the job search feature, everything I need—the screen, its specific widgets, its providers—is in one place. It stops me from jumping all over the project and helps keep my focus razor-sharp.

Installing Dependencies (The CLI Way)

HACK: Never Edit pubspec.yaml Manually

Manually adding packages to pubspec.yaml is a recipe for indentation errors and versioning headaches. Using flutter pub add is faster, safer, and adds the latest compatible version automatically. It also formats the file for you. It's a small change that screams professionalism.

Open your terminal and let's add our arsenal.

# Core stuff
$ flutter pub add provider dio url_launcher flutter_remix json_annotation cached_network_image google_fonts shared_preferences flutter_html fl_chart flutter_dotenv uuid

# Dev stuff for code generation
$ flutter pub add --dev build_runner json_serializable flutter_lints

Your pubspec.yaml should now look something like this:

name: job_tracker_app
description: "A new Flutter project."
# ... (rest of the file)

Final Setup Step: Create a .env file in your project root. This is where we'll store our precious API key. Do not commit this file to Git.

.env:

RAPIDAPI_KEY="YOUR_SUPER_SECRET_API_KEY_HERE"

Part 2: The Foundation - Core & "Brutalist Efficiency" Theme

A solid foundation makes building the rest of the house easy. Our core directory is that foundation.

The Design System

I wanted a look that was bold and unapologetic. Black, white, and a single, loud accent color: a vibrant yellow (#FFDB08).

Design Tip: The Power of "Brutalist Efficiency"

My design choices were intentional. In AppTheme, you'll see borderRadius is set to 0 and borderSide widths are 2.0px. This creates sharp, defined edges. There are no gradients or drop shadows. This isn't just a style; it's a philosophy. It communicates function over fluff, which was exactly the mindset I needed for my job hunt. The UI feels solid, dependable, and a little bit aggressive—perfect for an app named Jobstracker.

Core Widgets & Utilities

A good developer is a lazy developer. I built a set of reusable core widgets to save time, including BorderedCard, AppButton, and standardized LoadingStateWidget & ErrorStateWidget.

Performance HACK: Debouncer & Throttler

These two tiny utility classes in lib/core/utils are performance superheroes.

  • Debouncer: Used in the filter's location search. It prevents an API call on every keystroke. Instead, it waits until the user has stopped typing for 400ms. This feels more responsive to the user and saves dozens of useless API calls.

  • Throttler: Used on the salary RangeSlider. Without it, the slider would rebuild the UI on every single pixel of movement, causing jank. The throttler ensures the UI only updates a few times per second, making it feel buttery smooth while dragging.

Part 3: Talking to the API - The Art of Defensive Coding

The Hidden Job Board API is powerful, but like many APIs, its responses can be a bit wild. A fragile app would crash. Jobstracker is not a fragile app.

HACK: The Art of Defensive API Consumption

My ApiService is a fortress. It assumes the API will be inconsistent.

  1. _extractJobsArray: Instead of just looking for responseData['data'], this method is a hunter. It checks for jobs, data, results, and if all else fails, it finds the first top-level list in the response. This makes my app resilient to backend changes I don't control.

  2. _normalizeJobData: This is my sanitation plant. It takes the messy JSON and cleans it. It maps multiple possible keys (applyUrl, apply_url) to one consistent key in my Job model. It provides default values for missing data ('No Title'). It even generates a Uuid if a job is missing an ID. This single function prevents a hundred potential null-pointer crashes. Build for the messy real world, not the perfect documentation.

  3. Custom Exceptions: Instead of letting Dio throw generic errors, I catch them and throw my own, like RateLimitException or AuthenticationException. This lets the UI show a specific, helpful message like "Too many requests. Try again later," instead of "DioException: Error 429."

Part 4: The Brains - Models & State Management

With the API handled, we need to structure our data and manage the app's state.

Data Models & Code Generation

I used json_serializable to automate the creation of fromJson and toJson methods.

Pro Tip: Master Your Build Command

Don't just run dart run build_runner build. Use the --delete-conflicting-outputs flag. This command tells the build runner to overwrite old generated files. It solves a ton of frustrating "file already exists" errors and ensures your generated code is always fresh.

$ dart run build_runner build --delete-conflicting-outputs

State Management with Provider

We have three key providers:

  1. JobSearchProvider: Manages the state of the job search screen: the SearchState (initial, loading, loaded, error), the list of jobs, and the current search filters.

  2. ApplicationTrackerProvider: The heart of the tracking feature. It uses SharedPreferences to save and load a map of tracked jobs, providing methods like trackJob, untrackJob, and updateJobStatus.

  3. StatsProvider: Calculates all the analytics for our dashboard. It depends on ApplicationTrackerProvider.

State Management HACK: ChangeNotifierProxyProvider

This is Provider's secret weapon. My StatsProvider needs data from ApplicationTrackerProvider. Instead of passing it in manually, I use ChangeNotifierProxyProvider in main.dart.

It creates a reactive link. Whenever I track a new job in ApplicationTrackerProvider, it automatically triggers StatsProvider to update. The analytics dashboard rebuilds itself with the new data, instantly. It’s elegant, efficient, and lets my providers talk to each other without messy callbacks.

Part 5: Building the UI - Screens & Features

This is where it all comes together.

The HomeScreen and Navigation

The HomeScreen is the root UI after launch. It uses a BottomNavigationBar and an IndexedStack.

Navigation HACK: IndexedStack for State Preservation

The HomeScreen uses an IndexedStack to manage the three main tabs. Why not a simple _screens[_selectedIndex]? Because IndexedStack keeps all child widgets alive in the widget tree. This means when you're on the Tracker tab and switch back to the Search tab, your previous search results and scroll position are still there. It's a massive UX win that costs you one extra word.

Feature 1: The Job Search (features/job_search)

This is the entry point for finding new opportunities. The JobSearchScreen uses a Consumer<JobSearchProvider> to react to state changes. The FiltersBottomSheet is packed with controls, using our Debouncer and Throttler for performance. The JobListItem is where the brutalist theme shines, with its sharp BorderedCard and high-contrast elements

.

Feature 2: The Job Detail (features/job_detail)

This screen shows the full details of a single job.

Time-Saving HACK: flutter_html is Your Best Friend

The job descriptions from the API are raw HTML. Parsing this and trying to create a rich text view with Flutter widgets would be a nightmare. The flutter_html package does it all for you in one widget. I just fed it the HTML string and provided a style map to match my app's typography. It saved me at least a full day of tedious work.

Feature 3: The Tracker (features/tracker)

This is my personal command center. The TrackerScreen lists all the jobs I've saved.

UI HACK: Total Control with Custom Widgets

You'll notice the status filter chips aren't the standard FilterChip widget. They are GestureDetectors wrapping a custom-styled Container. Why? The default Material chips are hard to style precisely. By building my own, I get exact control over the border, color, and text style in both selected and unselected states, perfectly matching my theme.

The TrackedJobListItem features a PopupMenuButton that allows me to quickly change an application's status, which then updates the state and persists the change.

Feature 4: The Analytics (features/stats)

This screen turns my job hunt from a feeling into a science. It consumes StatsProvider and uses fl_chart to render three powerful visualizations:

  • ApplicationStatusChart: A pie chart showing my application distribution.

  • TimeSpentChart: A bar chart showing the average time an application spends in each stage. This told me my bottleneck was getting a response after applying.

  • ApplicationTrendChart: A line chart showing my cumulative applications, proving I was putting in the work.

Part 6: Victory - The Result

After a week of intense, focused work, Job was born. I started using it immediately. I could search faster, track my progress meticulously, and the analytics showed me where I was winning and losing.

The Ultimate HACK: Your Project IS Your Resume

In the interviews, I didn't just talk about my experience. I screen-shared Jobstracker.

  • When they asked about state management, I showed them the ChangeNotifierProxyProvider and explained why it was the perfect tool for reactive analytics.

  • When they asked about API integration, I walked them through my defensive parsing strategy in ApiService.

  • When they asked about UI/UX, I explained the "Brutalist Efficiency" design philosophy.

The app wasn't just a project on my resume; it was a living, breathing demonstration of my skills, my problem-solving ability, and my drive. It proved I could deliver, end-to-end.

The result? In the following week, I landed three interviews for senior Flutter positions. Two weeks later, I accepted an offer.

I was no longer the guy who was jobless for six months. I was the guy who built his own solution and took control. This app didn't just find me a job; it reminded me that as developers, we have the power to build the tools we need to solve our own problems.

So, if you're out there on the hamster wheel, maybe it's time to get off and build your own racetrack.


You can find the complete source code for this project on GitHub: https://github.com/medwonuola/job_tracker_app

2
Subscribe to my newsletter

Read articles from Ahmed Arigbabu directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ahmed Arigbabu
Ahmed Arigbabu

I'm a creative problem solver. I transform abstract concepts into practical and functional products that improve and enrich people's lives. I strive to create intuitive and visually appealing websites, engaging and responsive mobile apps, and streamlined and optimized business processes. My mission is to craft solutions that are not only functional but also visually captivating, aiming to make lasting and memorable impacts on users' lives.