Skip to content

tkshsbcue/Growth

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 

Repository files navigation

Growth

Mutual Fund Explorer & Portfolio Tracker
React Native · Expo SDK 54 · TypeScript

Platform Expo SDK React Native


Overview

Growth is a cross-platform mobile application for discovering mutual funds, viewing NAV performance history through interactive charts, and organizing funds into custom watchlist portfolios. All fund data is sourced from the public MFAPI endpoint.

Submitted for the Groww Mobile SDE Intern Assignment.


Demo

iOS Android
growth_ios.mp4
growth_android.mp4

Links

Resource Link
APK Download APK
Video Demo Google Drive

Table of Contents


Features

Explore

  • Four curated categories: Index Funds, Bluechip Funds, Tax Saver (ELSS), Large Cap Funds.
  • Each category renders a 2×2 grid of fund cards (Fund Name, AMC logo, latest NAV) with a View All CTA.
  • Pull-to-refresh support.
  • Since MFAPI lacks a category endpoint, the Search API is queried with relevant keywords (index, bluechip, tax saver, large cap) to populate each section.

Watchlist

  • Dedicated tab for user-created portfolio folders (e.g. "Retirement", "Tax Savers").
  • Summary strip showing total portfolios and tracked funds.
  • Create and delete portfolios via bottom sheet and confirmation dialog.
  • Skia-drawn empty state illustration with descriptive text and CTA when no portfolios exist.

Fund Details

  • AMC Name, Scheme Type (Growth / IDCW), Category, and latest NAV with daily change.
  • Interactive NAV line chart (Victory Native + Skia) with 6M / 1Y / ALL range selector.
  • Raw NAV data downsampled to 180 points via bucket-averaging for smooth rendering.
  • Bookmark toggle reflecting whether the fund exists in any watchlist folder.
  • Toast notification on successful portfolio addition.

Add to Portfolio (Bottom Sheet)

  • Triggered from the Fund Details screen.
  • Checkbox list of existing portfolios with inline create-new input.
  • Keyboard-aware sheet with auto-expand on input focus.

View All & Search

  • View All: Infinite-scroll list powered by @shopify/flash-list with pagination skeleton loaders.
  • Search: Debounced input (300ms), recent suggestions, animated transitions across recent / loading / results / empty / error states.

Architecture

┌──────────────────────────────────────────────┐
│                  Screens                      │
│           app/ (Expo Router)                  │
├──────────────────────────────────────────────┤
│                Components                     │
│    primitives/  explore/  fund/  watchlist/   │
├──────────────────────────────────────────────┤
│              Hooks Layer                      │
│  use-category-funds    use-fund-details       │
│  use-search-funds      use-folder-funds       │
├───────────────────┬──────────────────────────┤
│    Services       │        Stores             │
│  (Axios + MMKV)   │   (Zustand + MMKV)        │
├───────────────────┴──────────────────────────┤
│            Types & Constants                  │
└──────────────────────────────────────────────┘
Layer Responsibility
Screens File-based routing via Expo Router. Custom animated tab bar. Stack navigation for detail screens.
Components Design-system primitives (Screen, Text, Card, Button, Chip, Skeleton, StateView) and feature-specific components. Zero hardcoded colors — all primitives consume theme tokens.
Hooks React Query hooks encapsulating data-fetching logic. Each returns typed data with isLoading, isError, and derived states.
Services Axios HTTP client configured for MFAPI.in. MMKV storage adapters for Zustand and React Query persistence.
Stores Zustand store with MMKV middleware for watchlist state. Reactive — mutations propagate instantly across all screens.

Tech Stack

Category Library Version
Framework React Native 0.81
Platform Expo SDK 54
Language TypeScript 5.9
Routing Expo Router 6
State Management Zustand 5
Server State TanStack React Query 5
HTTP Axios 1.15
Storage react-native-mmkv 3
Charts Victory Native (Skia) 41
Graphics @shopify/react-native-skia 2.2
Lists @shopify/flash-list 2.3
Animation react-native-reanimated 4.1
Bottom Sheet @gorhom/bottom-sheet 5.2
Haptics expo-haptics 15
Images expo-image 3
Fonts expo-font 14
Testing Jest + jest-expo 29 / 55

Project Structure

growth/
├── app/                           # Screens (Expo Router)
│   ├── _layout.tsx                # Root layout — providers, splash, font loading
│   ├── search.tsx                 # Search with debounced input
│   ├── (tabs)/
│   │   ├── _layout.tsx            # Animated tab bar (Ionicons + Reanimated)
│   │   ├── index.tsx              # Explore — category grids
│   │   └── watchlist.tsx          # Portfolio folders + create sheet
│   ├── fund/[code].tsx            # Fund detail — chart, info, bookmark
│   ├── category/[key].tsx         # Category listing — infinite scroll
│   └── folder/[id].tsx            # Portfolio detail — fund list
│
├── components/
│   ├── primitives/                # Design-system atoms (15 components)
│   ├── explore/                   # Category grid, fund cards
│   ├── fund/                      # Chart, range selector, info row, bookmark
│   ├── watchlist/                 # Folder card, add-to-watchlist sheet
│   └── illustrations/            # Skia empty-state illustrations
│
├── hooks/
│   ├── use-theme.ts               # System color scheme + MMKV persistence
│   ├── use-haptic.ts              # expo-haptics wrapper
│   ├── use-debounced-value.ts     # Generic debounce (300ms default)
│   └── queries/                   # React Query hooks (4 hooks)
│
├── services/
│   ├── api.ts                     # Axios instance (api.mfapi.in)
│   ├── mmkv.ts                    # MMKV adapters for Zustand + React Query
│   └── mfapi.ts                   # searchFunds, getFundLatest, getFundDetails
│
├── stores/
│   └── watchlist-store.ts         # Zustand + MMKV persistence
│
├── constants/
│   ├── theme.ts                   # Colors, Spacing, Radius, Typography
│   ├── categories.ts              # Category definitions + search keywords
│   └── amc-logos.ts               # Fund house → CDN logo URL mapping
│
├── types/                         # TypeScript type definitions
├── utils/                         # format, downsample, color-from-string
├── __tests__/                     # 7 test suites (69 tests)
└── assets/                        # Fonts (Clash Display), icons, splash

State Management

Client State — Zustand + MMKV

interface WatchlistState {
  folders: Folder[];
  createFolder: (name: string) => string;
  deleteFolder: (folderId: string) => void;
  toggleFundInFolder: (schemeCode: number, folderId: string) => void;
  isFundInAnyFolder: (schemeCode: number) => boolean;
  getFoldersContainingFund: (schemeCode: number) => string[];
}

Persisted to MMKV via zustand/middleware. Toggling a fund on the detail screen instantly updates the bookmark icon and all watchlist views.

Server State — React Query

Hook Purpose Stale Time
useCategoryFunds Explore grid — search + top 4 latest NAVs 3h
useFundDetails Fund detail — full NAV history with date range 3h
useSearchFunds Search — debounced with keepPreviousData 6h
useFolderFunds Portfolio detail — parallel latest fetches 3h

Data Fetching & Caching

API Endpoints

Endpoint Usage
GET /mf/search?q={query} Explore categories, Search screen
GET /mf/{code}/latest Fund cards, portfolio fund lists
GET /mf/{code} Fund detail (full NAV history + metadata)

Caching Strategy

  • React Query handles request deduplication, automatic retry (2 attempts, exponential backoff), and stale-while-revalidate.
  • MMKV-backed persister saves the query cache to disk with a 7-day max age — the app serves cached data when offline.
  • Stale times are tuned per query type to balance freshness with API load.

Explore Screen Data Strategy

Since MFAPI has no category endpoint, each category is configured with a searchKeyword. useCategoryFunds queries the Search API, ranks results by relevance, then fetches the latest NAV for the top 4. Results are cached for instant subsequent loads.


Persistence

Data Storage Mechanism
Watchlist folders & fund associations MMKV Zustand persist middleware
API response cache MMKV React Query PersistQueryClientProvider
Theme preference MMKV Zustand store (system default)

All persistence uses react-native-mmkv — a C++ JSI-based key-value store, significantly faster than AsyncStorage.


UI State Handling

Every screen implements explicit Loading, Error, and Empty states:

Screen Loading Error Empty
Explore Staggered skeleton sections StateView with retry Detected when all categories return 0 funds
Search 5 skeleton rows Network error icon with retry Illustration with query text
Fund Detail Full-page skeleton Error fallback message N/A
Category 5 skeleton rows StateView with retry StateView empty state
Watchlist N/A (local) N/A (local) Illustration with CTA
Portfolio Detail 5 skeleton rows Alert icon with retry Illustration with "Explore Funds" CTA

Skeleton loaders use Reanimated opacity pulses with staggered delays for a cascading effect. The reusable StateView component manages state transitions:

type UiState = 'loading' | 'success' | 'error' | 'empty';

Testing

Framework: Jest 29 with jest-expo preset
Libraries: @testing-library/react-native, @testing-library/jest-native

 PASS  __tests__/watchlist-store.test.ts   (15 tests)
 PASS  __tests__/format.test.ts            (14 tests)
 PASS  __tests__/amc-logos.test.ts         (10 tests)
 PASS  __tests__/mfapi.test.ts             (8 tests)
 PASS  __tests__/downsample.test.ts        (7 tests)
 PASS  __tests__/categories.test.ts        (5 tests)
 PASS  __tests__/color-from-string.test.ts (5 tests)

 Test Suites:  7 passed, 7 total
 Tests:       69 passed, 69 total
Suite Coverage
watchlist-store Create/delete folders, toggle funds, query fund membership
format INR formatting, NAV delta, date parsing, scheme type detection
amc-logos URL resolution, whitespace normalization, keyword fallback
mfapi API request params, error propagation, edge cases (mocked Axios)
downsample Bucket-averaging accuracy, edge cases, large datasets
categories Category keys, labels, search keywords
color-from-string Deterministic generation, consistency, uniqueness
npm test

Getting Started

Prerequisites

  • Node.js >= 18
  • Xcode 16+ (iOS) or Android Studio (Android)

Setup

# Clone
git clone https://github.com/tkshsbcue/Growth.git
cd Growth/growth

# Install
npm install

# Generate native projects
npx expo prebuild

# iOS
npx expo run:ios

# Android
npx expo run:android

Run on a specific simulator

npx expo run:ios --device "iPhone Air"
npx expo run:android --device "Pixel_7_API_34"

Clear cache

npx expo start --clear

Design Decisions

TypographyClash Display (Semibold) by Indian Type Foundry is used for the "Growth" brand title. Loaded via Font.loadAsync with a graceful system-font fallback. All other text uses platform defaults (SF Pro / Roboto).

Splash Screen — Animated brand reveal: fade in (400ms) → hold (1s) → fade out (400ms) before the main app renders.

Theming — Follows the system color scheme automatically. 19 semantic color tokens with full light and dark implementations. Status bar adapts dynamically.

Tab Bar — Animated Ionicons (compass / bookmark) with spring-based bounce, color crossfade, and underline indicator.

AMC Logos — 50+ fund house names mapped to Groww CDN URLs. Whitespace normalization handles API inconsistencies. Keyword-based fuzzy matching as fallback. Unknown AMCs render a deterministic colored monogram.

Chart Performance — NAV data is downsampled to 180 points via bucket-averaging. Range selection scopes data server-side via API params.

Haptic Feedback — Applied throughout: card presses, button taps, tab switches, bookmark toggles, search actions, chip selections, and portfolio operations.

Offline Support — React Query cache persisted to MMKV (7-day max age). Watchlist data is also MMKV-persisted. The app remains functional with cached data when offline.


Evaluation Criteria

Criteria Status Details
Architecture Layered architecture with Zustand, clean folder structure, separation of concerns
State Management Zustand 5 for client state, React Query 5 for server state
Persistence MMKV for watchlist folders and fund associations
Local Caching MMKV-backed React Query cache, works offline with stale data
State Handling Explicit Loading, Error, and Empty states on all screens
Brownie Points
Modern Declarative UI 100% functional components with Hooks
Search Debouncing 300ms debounce via custom hook, 2-char minimum
Theme Support System-driven light/dark with 19 semantic tokens
Unit Tests 7 suites, 69 tests — stores, utilities, API, constants
Animations Reanimated 4 — springs, skeleton pulses, tab animations, toast, transitions

API Reference

All data from MFAPI:

Endpoint Description
GET /mf/search?q={query} Search funds by name
GET /mf/{scheme_code}/latest Latest NAV for a fund
GET /mf/{scheme_code} Full NAV history + metadata

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors