├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ ├── config.yml
│ ├── feature-request.md
│ └── new-tool.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── deploy.yml
│ ├── pr-build.yml
│ └── validate.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── bun.lock
├── components.json
├── data
├── _template.json
├── aapt2.json
├── adb-android-debug-bridge.json
├── adb-idea.json
├── apk-analyzer.json
├── apkanalyzer.json
├── apksigner.json
├── avdmanager.json
├── benchart.json
├── benchmarkify.json
├── bmgr.json
├── bundletool.json
├── bytemask.json
├── capture-and-read-bug-reports.json
├── chucker.json
├── compose-layout-inspector.json
├── compose-report-to-html.json
├── compose-ui-check.json
├── composition-tracing.json
├── d8.json
├── database-inspector.json
├── debug-apk.json
├── dependency-tree-diff.json
├── device-explorer.json
├── dex-diff.json
├── diffetto.json
├── diffuse.json
├── dmtracedump.json
├── dumpsys.json
├── editor-actions.json
├── etc1tool.json
├── firebender-plugin.json
├── gfx-avg.json
├── jetifier.json
├── jobb.json
├── kotlin-bench.json
├── layout-inspector.json
├── leakcanary.json
├── live-edit.json
├── logcat-command-line-tool.json
├── mesh.json
├── mksdcard.json
├── network-inspector.json
├── perf-sheet.json
├── perfetto.json
├── preview-animation.json
├── preview-ui.json
├── r8-retrace.json
├── record-a-video.json
├── relay-design-to-code.json
├── sdkmanager.json
├── sqlite3.json
├── take-a-screenshot.json
├── toolargetool.json
├── workmanager-worker-inspector.json
└── zipalign.json
├── eslint.config.js
├── index.html
├── package-lock.json
├── package.json
├── public
├── 404.html
├── apple-touch-icon.png
├── favicon-96x96.png
├── favicon.ico
├── favicon.svg
├── site.webmanifest
├── vite.svg
├── web-app-manifest-192x192.png
└── web-app-manifest-512x512.png
├── scripts
├── breakdown.js
├── merge.js
├── update-authors.js
└── validate.js
├── src
├── App.css
├── App.tsx
├── assets
│ └── react.svg
├── components
│ └── ui
│ │ ├── card.tsx
│ │ ├── floating-action-button.tsx
│ │ ├── footer.tsx
│ │ ├── github-stats.tsx
│ │ ├── header.tsx
│ │ ├── input.tsx
│ │ ├── masonry-grid.tsx
│ │ ├── modal.tsx
│ │ ├── pagination.tsx
│ │ ├── search-bar.tsx
│ │ └── tool-of-the-day.tsx
├── index.css
├── lib
│ └── utils.ts
├── main.tsx
└── vite-env.d.ts
├── tailwind.config.js
├── tailwind.config.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | about: Report a problem with the website or data
4 | title: "[BUG] "
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Bug Description
11 |
12 |
13 | ## Steps To Reproduce
14 |
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | ## Expected Behavior
21 |
22 |
23 | ## Screenshots
24 |
25 |
26 | ## Environment
27 | - Device: [e.g. Desktop, iPhone, Pixel]
28 | - OS: [e.g. Windows, macOS, iOS, Android]
29 | - Browser: [e.g. Chrome, Safari]
30 | - Version: [e.g. 22]
31 |
32 | ## Additional Context
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Questions or Discussions
4 | url: https://github.com/yogeshpaliyal/awesome-android-tooling/discussions
5 | about: Please ask and answer questions here.
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request
3 | about: Suggest a new feature or enhancement for the website
4 | title: "[FEATURE] "
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Feature Description
11 |
12 |
13 | ## Problem This Solves
14 |
15 |
16 | ## Proposed Solution
17 |
18 |
19 | ## Alternatives Considered
20 |
21 |
22 | ## Additional Context
23 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/new-tool.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Add New Tool
3 | about: Suggest a new Android development tool to add to the collection
4 | title: "[NEW TOOL] "
5 | labels: new-tool
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Tool Information
11 |
12 | **Tool Name:**
13 |
14 |
15 | **Description:**
16 |
17 |
18 | **Documentation Link:**
19 |
20 |
21 | **Tool Category:**
22 |
23 | - [ ] Command Line Tool
24 | - [ ] Android Studio Tool
25 | - [ ] Jetpack Compose Tool
26 | - [ ] Other (please specify)
27 |
28 | **Tags:**
29 |
30 |
31 | ## Additional Information
32 |
33 | **Why is this tool valuable for Android developers?**
34 |
35 |
36 | **Screenshots or Examples:**
37 |
38 |
39 | **Additional context:**
40 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 |
4 | ## Type of Change
5 |
6 | - [ ] New tool addition
7 | - [ ] Bug fix (non-breaking change that fixes an issue)
8 | - [ ] New feature (non-breaking change that adds functionality)
9 | - [ ] Documentation update
10 | - [ ] Code refactoring
11 | - [ ] Other (please describe):
12 |
13 | ## How Has This Been Tested?
14 |
15 | - [ ] I've validated the JSON data with `bun run validate`
16 | - [ ] I've tested the website locally with `bun run dev`
17 | - [ ] I've verified all links work correctly
18 |
19 | ## Checklist
20 |
21 | - [ ] My code follows the code style of this project
22 | - [ ] I have updated the documentation as needed
23 | - [ ] My changes generate no new warnings or errors
24 | - [ ] For new tool additions:
25 | - [ ] I've included all required fields (name, description, link, tags)
26 | - [ ] The link is valid and points to official documentation
27 | - [ ] The description is clear and concise
28 | - [ ] I've added appropriate tags
29 |
30 | ## Screenshots
31 |
32 |
33 | ## Additional Context
34 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy static content to Pages
2 |
3 | on:
4 | # Runs on pushes targeting the default branch
5 | push:
6 | branches: ['master']
7 |
8 | # Allows you to run this workflow manually from the Actions tab
9 | workflow_dispatch:
10 |
11 | # Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages
12 | permissions:
13 | contents: read
14 | pages: write
15 | id-token: write
16 |
17 | # Allow one concurrent deployment
18 | concurrency:
19 | group: 'pages'
20 | cancel-in-progress: true
21 |
22 | jobs:
23 | # Single deploy job since we're just deploying
24 | deploy:
25 | environment:
26 | name: github-pages
27 | url: ${{ steps.deployment.outputs.page_url }}
28 | runs-on: ubuntu-latest
29 | steps:
30 | - name: Checkout
31 | uses: actions/checkout@v4
32 | - name: Set up Bun
33 | uses: oven-sh/setup-bun@v2
34 |
35 |
36 | - name: Install dependencies
37 | run: bun install --frozen-lockfile
38 |
39 | - name: Build
40 | run: npm run build
41 |
42 | - name: Setup Pages
43 | uses: actions/configure-pages@v4
44 |
45 | - name: Upload artifact
46 | uses: actions/upload-pages-artifact@v3
47 | with:
48 | # Upload dist folder
49 | path: './dist'
50 | - name: Deploy to GitHub Pages
51 | id: deployment
52 | uses: actions/deploy-pages@v4
--------------------------------------------------------------------------------
/.github/workflows/pr-build.yml:
--------------------------------------------------------------------------------
1 | name: Build for PR
2 |
3 | on:
4 | pull_request:
5 | branches: [ "master" ]
6 | paths-ignore:
7 | - 'data/**/*.json'
8 | # Allow manual triggering of the workflow
9 | workflow_dispatch:
10 |
11 | # Add explicit permissions for the GITHUB_TOKEN
12 | permissions:
13 | contents: read
14 | pull-requests: write # Required to add comments to PRs
15 |
16 | jobs:
17 | build:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Checkout repository
21 | uses: actions/checkout@v3
22 |
23 | - name: Setup Bun
24 | uses: oven-sh/setup-bun@v1
25 | with:
26 | bun-version: latest
27 |
28 | - name: Install dependencies
29 | run: bun install
30 |
31 | - name: 🛠️ Build
32 | run: bun run build
33 |
--------------------------------------------------------------------------------
/.github/workflows/validate.yml:
--------------------------------------------------------------------------------
1 | name: Validate Data Files
2 |
3 | on:
4 | pull_request:
5 | branches: [ "master" ]
6 | paths:
7 | - 'data/**/*.json'
8 | # Allow manual triggering of the workflow
9 | workflow_dispatch:
10 |
11 | # Add explicit permissions for the GITHUB_TOKEN
12 | permissions:
13 | contents: read
14 | pull-requests: write # Required to add comments to PRs
15 |
16 | jobs:
17 | validate:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Checkout repository
21 | uses: actions/checkout@v3
22 |
23 | - name: Setup Bun
24 | uses: oven-sh/setup-bun@v1
25 | with:
26 | bun-version: latest
27 |
28 | - name: Install dependencies
29 | run: bun install
30 |
31 | - name: Validate JSON files
32 | run: bun run validate
33 |
34 | - name: Run merge test
35 | run: bun run node scripts/merge.js
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | .vite
16 |
17 | src/data.json
18 |
19 | # Editor directories and files
20 | .vscode/*
21 | !.vscode/extensions.json
22 | .idea
23 | .DS_Store
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw?
29 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8 |
9 | ## Our Standards
10 |
11 | Examples of behavior that contributes to a positive environment for our community include:
12 |
13 | * Demonstrating empathy and kindness toward other people
14 | * Being respectful of differing opinions, viewpoints, and experiences
15 | * Giving and gracefully accepting constructive feedback
16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17 | * Focusing on what is best not just for us as individuals, but for the overall community
18 |
19 | Examples of unacceptable behavior include:
20 |
21 | * The use of sexualized language or imagery, and sexual attention or advances of any kind
22 | * Trolling, insulting or derogatory comments, and personal or political attacks
23 | * Public or private harassment
24 | * Publishing others’ private information, such as a physical or email address, without their explicit permission
25 | * Other conduct which could reasonably be considered inappropriate in a professional setting
26 |
27 | ## Enforcement Responsibilities
28 |
29 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
32 |
33 | ## Scope
34 |
35 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
36 |
37 | ## Enforcement
38 |
39 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly.
40 |
41 | All community leaders are obligated to respect the privacy and security of the reporter of any incident.
42 |
43 | ## Enforcement Guidelines
44 |
45 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
46 |
47 | ### 1. Correction
48 |
49 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
50 |
51 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
52 |
53 | ### 2. Warning
54 |
55 | **Community Impact**: A violation through a single incident or series of actions.
56 |
57 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
58 |
59 | ### 3. Temporary Ban
60 |
61 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
62 |
63 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
64 |
65 | ### 4. Permanent Ban
66 |
67 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
68 |
69 | **Consequence**: A permanent ban from any sort of public interaction within the community.
70 |
71 | ## Attribution
72 |
73 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
74 |
75 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
76 |
77 | For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
78 |
79 | [homepage]: https://www.contributor-covenant.org
80 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Add a New Tool
2 |
3 | This guide explains how to add a new Android tool to the Awesome Android Tooling collection.
4 |
5 | ## Prerequisites
6 |
7 | Before you begin, make sure you have:
8 |
9 | - Git installed on your machine
10 | - A GitHub account
11 | - Basic knowledge of JSON format
12 | - Forked the repository and cloned it locally
13 |
14 | ## Adding a New Tool
15 |
16 | You can add a new tool to the collection by following these steps:
17 |
18 | ### Option 1: Direct Pull Request (Simple Method)
19 |
20 | 1. **Fork the repository** if you haven't already
21 | 2. **Create a new branch** for your addition
22 | ```bash
23 | git checkout -b add-your-tool-name
24 | ```
25 | 3. **Use the "Submit Tool" button** on the website or submit a GitHub issue with the tool information
26 | 4. Alternatively, you can **directly edit** the data file
27 |
28 | ### Option 2: Adding to Individual JSON Files (Recommended)
29 |
30 | 1. **Create a new JSON file** in the `data` directory with a filename based on your tool name:
31 | ```bash
32 | cd data
33 | touch your-tool-name.json
34 | ```
35 |
36 | 2. **Add the tool information** using the following format:
37 | ```json
38 | {
39 | "name": "Your Tool Name",
40 | "description": "A detailed description of your tool explaining what it does and why it's useful for Android development. Aim for 1-3 sentences that clearly explain the purpose and value.",
41 | "link": "https://link-to-official-documentation-or-github-repo",
42 | "authorName": "Name of tool author or organization (optional)",
43 | "authorLink": "https://link-to-author-website-or-profile (optional)",
44 | "tags": [
45 | "relevant-tag-1",
46 | "relevant-tag-2",
47 | "relevant-tag-3",
48 | "relevant-tag-4"
49 | ]
50 | }
51 | ```
52 |
53 | 3. **Validate your JSON file** to ensure it's correctly formatted:
54 | ```bash
55 | npm run validate
56 | # or
57 | bun run validate
58 | ```
59 |
60 | 4. **Generate the combined data file** by running:
61 | ```bash
62 | npm run merge
63 | # or
64 | bun run merge
65 | ```
66 |
67 | ## Guidelines for Tool Submissions
68 |
69 | ### Required Fields
70 |
71 | Each tool entry must include:
72 |
73 | - **name**: The official name of the tool
74 | - **description**: A clear, concise description (10-300 characters)
75 | - **link**: URL to the official documentation or repository
76 | - **tags**: At least one descriptive tag (see existing tags below)
77 |
78 | ### Optional Fields
79 |
80 | You can also include these optional fields:
81 |
82 | - **authorName**: Name of the tool's author or maintaining organization
83 | - **authorLink**: URL to the author's website, GitHub profile, or other relevant link
84 |
85 | ### Common Tags
86 |
87 | Choose from these existing tags or add new ones when appropriate:
88 |
89 | - **Tool Types**: `command-line`, `android-studio`, `jetpack-compose`
90 | - **Functionality**: `debugging`, `testing`, `profiling`, `analysis`
91 | - **Focus Areas**: `ui`, `performance`, `optimization`, `development`
92 |
93 | ### Best Practices
94 |
95 | - Use clear, descriptive names that match the tool's official name
96 | - Provide concise but informative descriptions
97 | - Include a direct link to the official documentation whenever possible
98 | - Select relevant tags that accurately categorize the tool
99 | - Check that your tool isn't already in the collection
100 |
101 | ## Submitting Your Addition
102 |
103 | 1. **Commit your changes**:
104 | ```bash
105 | git add .
106 | git commit -m "Add [Your Tool Name]"
107 | ```
108 |
109 | 2. **Push to your fork**:
110 | ```bash
111 | git push origin add-your-tool-name
112 | ```
113 |
114 | 3. **Create a pull request** to the main repository
115 |
116 | 4. **Wait for review** - maintainers will review your submission and may request changes
117 |
118 | ## Example Submission
119 |
120 | Here's an example of a well-formatted tool submission:
121 |
122 | ```json
123 | {
124 | "name": "App Inspector",
125 | "description": "App Inspector allows you to examine the component hierarchy and properties of your Android app at runtime, helping you debug layout issues and understand how your UI components interact.",
126 | "link": "https://developer.android.com/example-tool",
127 | "authorName": "Android Team",
128 | "authorLink": "https://developer.android.com",
129 | "tags": [
130 | "android-studio",
131 | "debugging",
132 | "ui",
133 | "inspection"
134 | ]
135 | }
136 | ```
137 |
138 | ---
139 |
140 | Thank you for contributing to Awesome Android Tooling! Your submissions help make this resource more valuable for the entire Android development community.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Yogesh Choudhary Paliyal
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🛠️ Awesome Android Tooling
2 |
3 | [](https://github.yogeshpaliyal.com/awesome-android-tooling/)
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | A curated collection of essential tools for Android development
15 | Discover tools that can significantly improve your workflow when building, testing, and optimizing Android apps.
16 |
17 |
18 | ## 🌟 [View the Interactive Tool Collection](https://yogeshpaliyal.github.io/awesome-android-tooling/)
19 |
20 | ## 📑 Categories
21 |
22 |
23 | - [Command Line Tools](https://github.yogeshpaliyal.com/awesome-android-tooling/?tag=command-line) - Tools that run from your terminal
24 | - [Android Studio Tools](https://github.yogeshpaliyal.com/awesome-android-tooling/?tag=android-studio) - Built-in features to enhance your IDE experience
25 | - [Jetpack Compose Tools](https://github.yogeshpaliyal.com/awesome-android-tooling/?tag=jetpack-compose) - Tools specific to modern UI development
26 |
27 | ## ✨ Highlights
28 |
29 | - **45+ Tools** covering all aspects of Android development
30 | - **Detailed descriptions** of each tool's purpose and capabilities
31 | - **Direct links** to official documentation
32 | - **Categorized by type** for easy discovery
33 |
34 |
35 | ## 🤝 How to Contribute
36 |
37 | We welcome contributions! Adding new tools or improving the documentation is easy:
38 |
39 | 1. **Fork** the repository
40 | 2. **Add your tool** to the appropriate JSON file in the `data/` directory
41 | 3. **Validate** your changes with `bun run validate`
42 | 4. **Submit** a pull request
43 |
44 | See our detailed [contribution guide](CONTRIBUTING.md) for step-by-step instructions.
45 |
46 | ## 🚀 Getting Started with the Project
47 |
48 | If you want to run this project locally:
49 |
50 | ```bash
51 | # Clone the repository
52 | git clone https://github.com/yogeshpaliyal/awesome-android-tooling.git
53 |
54 | # Enter the project directory
55 | cd awesome-android-tooling
56 |
57 | # Install dependencies
58 | bun install
59 |
60 | # Run the development server
61 | bun run dev
62 | ```
63 |
64 | ## 📄 License
65 |
66 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
67 |
68 | ## 🙏 Acknowledgements
69 |
70 | - Thanks to all [contributors](https://github.com/yogeshpaliyal/awesome-android-tooling/graphs/contributors) who have helped make this resource better
71 | - Android is a trademark of Google LLC
72 |
73 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": false,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "src/index.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | },
20 | "iconLibrary": "lucide"
21 | }
--------------------------------------------------------------------------------
/data/_template.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "description": "",
4 | "link": "",
5 | "authorName": "",
6 | "authorLink": "",
7 | "tags": [
8 | "command-line"
9 | ]
10 | }
--------------------------------------------------------------------------------
/data/aapt2.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "AAPT2",
3 | "description": "AAPT2 (Android Asset Packaging Tool) is a build tool that Android Studio and Android Gradle Plugin use to compile and package your app's resources. AAPT2 parses, indexes, and compiles the resources into a binary format that is optimized for the Android platform.",
4 | "link": "https://developer.android.com/tools/aapt2",
5 | "tags": [
6 | "command-line",
7 | "build",
8 | "resources",
9 | "packaging"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/adb-android-debug-bridge.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ADB (Android Debug Bridge)",
3 | "description": "A command-line tool that facilitates communication between your development PC and Android devices, enabling debugging and testing.",
4 | "link": "https://developer.android.com/tools/adb",
5 | "tags": [
6 | "command-line",
7 | "debugging",
8 | "testing",
9 | "device-communication"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/adb-idea.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adb-idea",
3 | "description": "A plugin for Android Studio and Intellij IDEA that speeds up your day to day android development.",
4 | "link": "https://github.com/pbreault/adb-idea",
5 | "tags": [
6 | "android-studio",
7 | "plugin",
8 | "adb"
9 | ],
10 | "authorName": "pbreault",
11 | "authorLink": "https://github.com/pbreault"
12 | }
--------------------------------------------------------------------------------
/data/apk-analyzer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "APK Analyzer",
3 | "description": "Android Studio includes an APK Analyzer that provides immediate insight into the composition of your APK or Android App Bundle after the build process completes. Using the APK Analyzer can reduce the time you spend debugging issues with DEX files and resources within your app and help reduce your APK size. The APK Analyzer is also available from the command line with apkanalyzer.",
4 | "link": "https://developer.android.com/studio/debug/apk-analyzer",
5 | "tags": [
6 | "android-studio",
7 | "apk",
8 | "analysis",
9 | "optimization"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/apkanalyzer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "apkanalyzer",
3 | "description": "The command-line version of APK Analyzer provides immediate insight into the composition of your APK after the build process completes and lets you compare differences between two APKs. Using APK Analyzer reduces the time you spend debugging issues with DEX files and resources within your app and reduces the size of your APK.",
4 | "link": "https://developer.android.com/tools/apkanalyzer",
5 | "tags": [
6 | "command-line",
7 | "apk",
8 | "debugging",
9 | "analysis"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/apksigner.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "apksigner",
3 | "description": "The apksigner tool, available in revision 24.0.3 and higher of the Android SDK Build Tools, lets you sign APKs and confirm that an APK's signature will be verified successfully on all versions of the Android platform supported by that APK.",
4 | "link": "https://developer.android.com/tools/apksigner",
5 | "tags": [
6 | "command-line",
7 | "apk",
8 | "signing",
9 | "security"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/avdmanager.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "avdmanager",
3 | "description": "The avdmanager is a command-line tool that lets you create and manage Android Virtual Devices (AVDs) from the command line. An AVD lets you define the characteristics of an Android handset, Wear OS watch, or Android TV device that you want to simulate in the Android Emulator.",
4 | "link": "https://developer.android.com/tools/avdmanager",
5 | "tags": [
6 | "command-line",
7 | "emulator",
8 | "virtual-device",
9 | "testing"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/benchart.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "benchart",
3 | "description": "📊 A web tool to visualise and compare plain text data. Support Android's Macrobenchmark output",
4 | "link": "https://github.com/theapache64/benchart",
5 | "tags": [
6 | "macrobenchmark",
7 | "profiling",
8 | "compare"
9 | ],
10 | "authorName": "theapache64",
11 | "authorLink": "https://github.com/theapache64"
12 | }
--------------------------------------------------------------------------------
/data/benchmarkify.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Benchmarkify",
3 | "description": "Benchmarkify is a web app that helps you visualize and analyze Android macrobenchmark results stored in JSON format.",
4 | "link": "https://github.com/yogeshpaliyal/benchmarkify",
5 | "tags": [
6 | "macrobenchmark",
7 | "profiling"
8 | ],
9 | "authorName": "yogeshpaliyal",
10 | "authorLink": "https://github.com/yogeshpaliyal"
11 | }
--------------------------------------------------------------------------------
/data/bmgr.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bmgr",
3 | "description": "bmgr is a shell tool you can use to interact with the Backup Manager on Android devices version 2.2 (API Level 8) or higher. The tool provides commands to initiate backup and restore operations so that you don't need to repeatedly wipe data or take similar intrusive steps in order to test your application's backup functionality. The bmgr tool supports both Auto Backup and Key/Value Backup.",
4 | "link": "https://developer.android.com/tools/bmgr",
5 | "tags": [
6 | "command-line",
7 | "backup",
8 | "testing",
9 | "data-management"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/bundletool.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bundletool",
3 | "description": "bundletool is the underlying tool that Android Studio, the Android Gradle plugin, and Google Play use to build an Android App Bundle. bundletool can convert an app bundle into the various APKs that are deployed to devices.",
4 | "link": "https://developer.android.com/tools/bundletool",
5 | "tags": [
6 | "command-line",
7 | "app-bundle",
8 | "deployment",
9 | "packaging"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/bytemask.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Bytemask",
3 | "description": "Bytemask is an Android Gradle Plugin that masks your secret strings for the app in the source code making it difficult to extract from reverse engineering.",
4 | "link": "https://github.com/PatilShreyas/bytemask",
5 | "authorName": "PatilShreyas",
6 | "authorLink": "https://github.com/PatilShreyas",
7 | "tags": [
8 | "gradle-plugin",
9 | "security"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/data/capture-and-read-bug-reports.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Capture and read bug reports",
3 | "description": "A bug report contains device logs, stack traces, and other diagnostic information to help you find and fix bugs in your app. To capture a bug report from your device, use the Take bug report developer option on the device, the Android Emulator menu, or the adb bugreport command on your development machine.",
4 | "link": "https://developer.android.com/studio/debug/bug-report",
5 | "tags": [
6 | "android-studio",
7 | "debugging",
8 | "bug-reports",
9 | "diagnostics"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/chucker.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chucker",
3 | "description": "🔎 An HTTP inspector for Android & OkHTTP (like Charles but on device)",
4 | "link": "https://github.com/ChuckerTeam/chucker",
5 | "authorName": "ChuckerTeam",
6 | "authorLink": "https://github.com/ChuckerTeam",
7 | "tags": [
8 | "http-requests",
9 | "okhttp",
10 | "library"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/data/compose-layout-inspector.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Compose Layout Inspector",
3 | "description": "Inspect a Compose layout in an emulator or physical device.",
4 | "link": "https://developer.android.com/develop/ui/compose/tooling/debug#layout_inspector",
5 | "tags": [
6 | "jetpack-compose",
7 | "debugging",
8 | "layout",
9 | "inspection"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/compose-report-to-html.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Compose Compiler Reports to HTML Generator",
3 | "description": "A utility to convert Jetpack Compose compiler metrics and reports to beautified 😍 HTML page. Made with ❤️ for Android Developers and Composers",
4 | "link": "https://github.com/PatilShreyas/compose-report-to-html",
5 | "tags": [
6 | "jetpack-compose",
7 | "cli",
8 | "gradle-plugin",
9 | "report"
10 | ],
11 | "authorName": "PatilShreyas",
12 | "authorLink": "https://github.com/PatilShreyas"
13 | }
--------------------------------------------------------------------------------
/data/compose-ui-check.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Compose UI Check",
3 | "description": "Inspect Compose UI for accessibility and adaptive issues.",
4 | "link": "https://developer.android.com/develop/ui/compose/tooling/debug#compose_ui_check",
5 | "tags": [
6 | "jetpack-compose",
7 | "accessibility",
8 | "ui",
9 | "adaptive"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/composition-tracing.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Composition Tracing",
3 | "description": "Trace your composable functions in a system trace.",
4 | "link": "https://developer.android.com/develop/ui/compose/tooling/tracing",
5 | "tags": [
6 | "jetpack-compose",
7 | "performance",
8 | "tracing",
9 | "debugging"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/d8.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "d8",
3 | "description": "d8 is a command-line tool that Android Studio and the Android Gradle plugin use to compile your project's Java bytecode into DEX bytecode that runs on Android devices. d8 lets you use Java 8 language features in your app's code.",
4 | "link": "https://developer.android.com/tools/d8",
5 | "tags": [
6 | "command-line",
7 | "bytecode",
8 | "compilation",
9 | "java8"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/database-inspector.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Database Inspector",
3 | "description": "The Database Inspector lets you inspect, query, and modify your app's databases while your app is running. This is especially useful for database debugging. The Database Inspector works with plain SQLite and with libraries built on top of SQLite, such as Room.",
4 | "link": "https://developer.android.com/studio/inspect/database",
5 | "tags": [
6 | "android-studio",
7 | "database",
8 | "debugging",
9 | "room"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/debug-apk.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Debug APK",
3 | "description": "Android Studio 3.0 and higher lets you profile and debug APKs that have debugging enabled without having to build them from an Android Studio project.",
4 | "link": "https://developer.android.com/studio/debug/apk-debugger",
5 | "tags": [
6 | "android-studio",
7 | "apk",
8 | "debugging",
9 | "profiling"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/dependency-tree-diff.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Dependency Tree Diff",
3 | "description": "An intelligent diff tool for the output of Gradle's dependencies task which always shows the path to the root dependency.",
4 | "link": "https://github.com/JakeWharton/dependency-tree-diff",
5 | "tags": [
6 | "command-line"
7 | ],
8 | "authorName": "JakeWharton",
9 | "authorLink": "https://github.com/JakeWharton"
10 | }
--------------------------------------------------------------------------------
/data/device-explorer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Device Explorer",
3 | "description": "The Device Explorer lets you view, copy, and delete files on an Android device. It's useful when examining files your app creates or if you want to transfer files to and from a device.",
4 | "link": "https://developer.android.com/studio/debug/device-file-explorer",
5 | "tags": [
6 | "android-studio",
7 | "device",
8 | "file-management",
9 | "debugging"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/dex-diff.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dex-diff",
3 | "description": "A tool to compare two APK files at the dex level. Useful for checking the impact of things like fullMode and dex optimisations.",
4 | "link": "https://github.com/theapache64/dex-diff",
5 | "tags": [
6 | "APK",
7 | "dex",
8 | "compare"
9 | ],
10 | "authorName": "theapache64",
11 | "authorLink": "https://github.com/theapache64"
12 | }
--------------------------------------------------------------------------------
/data/diffetto.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "diffetto",
3 | "description": "A tool to differentiate between two Perfetto traces",
4 | "link": "https://github.com/theapache64/diffetto",
5 | "tags": [
6 | "perfetto",
7 | "profiling",
8 | "compare"
9 | ],
10 | "authorName": "theapache64",
11 | "authorLink": "https://github.com/theapache64"
12 | }
--------------------------------------------------------------------------------
/data/diffuse.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "diffuse",
3 | "description": "Diffuse is a tool for diffing APKs, AABs, AARs, and JARs in a way that aims to provide both a high-level view of what changes along with important detailed output.",
4 | "link": "https://github.com/JakeWharton/diffuse",
5 | "authorName": "Jake Wharton",
6 | "authorLink": "https://github.com/JakeWharton",
7 | "tags": [
8 | "command-line",
9 | "apk-diff"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/data/dmtracedump.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dmtracedump",
3 | "description": "dmtracedump is a tool that generates graphical call-stack diagrams from trace log files. The tool uses the Graphviz Dot utility to create the graphical output, so you need to install Graphviz before running dmtracedump. If you haven't yet generated trace logs and saved them from your connected device to your local machine, go to Generate trace logs by instrumenting your app.",
4 | "link": "https://developer.android.com/tools/dmtracedump",
5 | "tags": [
6 | "command-line",
7 | "debugging",
8 | "profiling",
9 | "call-stack"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/dumpsys.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dumpsys",
3 | "description": "dumpsys is a tool that runs on Android devices and provides information about system services. Call dumpsys from the command line using the Android Debug Bridge (ADB) to get diagnostic output for all system services running on a connected device.",
4 | "link": "https://developer.android.com/tools/dumpsys",
5 | "tags": [
6 | "command-line",
7 | "debugging",
8 | "system-services",
9 | "diagnostics"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/editor-actions.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Editor actions",
3 | "description": "Use templates, gutter icons, and more in the Android Studio editor window.",
4 | "link": "https://developer.android.com/develop/ui/compose/tooling/editor-actions",
5 | "tags": [
6 | "jetpack-compose",
7 | "editor",
8 | "templates",
9 | "development"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/etc1tool.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "etc1tool",
3 | "description": "etc1tool is a command line utility that lets you encode PNG images to the ETC1 compression standard and decode ETC1 compressed images back to PNG.",
4 | "link": "https://developer.android.com/tools/etc1tool",
5 | "tags": [
6 | "command-line",
7 | "images",
8 | "compression",
9 | "resources"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/firebender-plugin.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Firebender",
3 | "description": "👊🔥 A lightweight coding agent built specifically for Android Studio based on models like Claude 3.7 Sonnet and Gemini 2.5 Pro. Privacy-focused: no training or storage of your code data. Comes with fast autocomplete, cmdk, agent rules customization, mcp support, and more! 🚀",
4 | "link": "https://firebender.com",
5 | "authorName": "Kevin and Aman",
6 | "authorLink": "https://firebender.com/about",
7 | "tags": [
8 | "android-studio",
9 | "debugging",
10 | "development",
11 | "ai",
12 | "testing",
13 | "code-assistant"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/data/gfx-avg.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gfx-avg",
3 | "description": "A CLI tool to find average from multiple gfxinfo files",
4 | "link": "https://github.com/theapache64/gfx-avg",
5 | "tags": [
6 | "CLI",
7 | "gfxinfo"
8 | ],
9 | "authorName": "theapache64",
10 | "authorLink": "https://github.com/theapache64"
11 | }
--------------------------------------------------------------------------------
/data/jetifier.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Jetifier",
3 | "description": "The standalone Jetifier tool migrates support-library-dependent libraries to instead rely on the equivalent AndroidX packages. The tool lets you migrate an individual library directly instead of using the Android Gradle plugin bundled with Android Studio.",
4 | "link": "https://developer.android.com/tools/jetifier",
5 | "tags": [
6 | "command-line",
7 | "androidx",
8 | "migration",
9 | "libraries"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/jobb.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jobb",
3 | "description": "The jobb tool lets you build encrypted and unencrypted APK expansion files in Opaque Binary Blob (OBB) format. You can download and mount these expansion files in your application using StorageManager on devices with Android 2.3 (API Level 9) or higher. OBB files provide additional file assets for Android applications, such as graphics, sounds, and video, separate from an application's APK file. For more information on using expansion files, see APK Expansion Files.",
4 | "link": "https://developer.android.com/tools/jobb",
5 | "tags": [
6 | "command-line",
7 | "expansion-files",
8 | "obb",
9 | "assets"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/kotlin-bench.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Kotlin Bench",
3 | "description": "🔍 A benchmark for evaluating Large Language Models (LLMs) on real-world Kotlin and Android software engineering tasks. The first benchmark that tests AI models' ability to resolve actual Kotlin and Android issues. Created by the Firebender team 🔥.",
4 | "link": "https://github.com/firebenders/Kotlin-bench",
5 | "authorName": "Aman Gottumukkala",
6 | "authorLink": "https://firebender.com/blog/kotlin-bench",
7 | "tags": [
8 | "kotlin",
9 | "benchmark",
10 | "android",
11 | "llm",
12 | "ai"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/data/layout-inspector.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Layout Inspector",
3 | "description": "Enables you to inspect and debug the layout of your UI in an emulator or physical device.",
4 | "link": "https://developer.android.com/studio/debug/layout-inspector",
5 | "tags": [
6 | "android-studio",
7 | "ui",
8 | "debugging",
9 | "inspection"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/leakcanary.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "leakcanary",
3 | "description": "LeakCanary’s knowledge of the internals of the Android Framework gives it a unique ability to narrow down the cause of each leak, helping developers dramatically reduce jank, Application Not Responding freezes and OutOfMemoryError crashes.",
4 | "link": "https://square.github.io/leakcanary/",
5 | "authorName": "Square",
6 | "authorLink": "https://github.com/square",
7 | "tags": [
8 | "memory-leak"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/data/live-edit.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Live Edit",
3 | "description": "Apply changes and see them in real time without doing full builds.",
4 | "link": "https://developer.android.com/develop/ui/compose/tooling/iterative-development",
5 | "tags": [
6 | "jetpack-compose",
7 | "development",
8 | "real-time",
9 | "editing"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/logcat-command-line-tool.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Logcat command-line tool",
3 | "description": "Logcat is a command-line tool that dumps a log of system messages including messages that you have written from your app with the Log class.",
4 | "link": "https://developer.android.com/tools/logcat",
5 | "tags": [
6 | "command-line",
7 | "logging",
8 | "debugging",
9 | "diagnostics"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/mesh.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Mesh",
3 | "description": "A simple tool to create and edit mesh gradients.",
4 | "link": "https://github.com/c5inco/Mesh",
5 | "tags": [
6 | "jetpack-compose",
7 | "gradient"
8 | ],
9 | "authorName": "c5inco",
10 | "authorLink": "https://github.com/c5inco"
11 | }
--------------------------------------------------------------------------------
/data/mksdcard.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mksdcard",
3 | "description": "Use the mksdcard tool to create a FAT32 disk image that you can load into emulators running different Android Virtual Devices (AVDs) to simulate the presence of the same SD card in multiple devices.",
4 | "link": "https://developer.android.com/tools/mksdcard",
5 | "tags": [
6 | "command-line",
7 | "emulator",
8 | "virtual-device",
9 | "storage"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/network-inspector.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Network Inspector",
3 | "description": "The Network Inspector displays real-time network activity on a timeline, showing data sent and received. The Network Inspector lets you examine how and when your app transfers data and optimize the underlying code appropriately.",
4 | "link": "https://developer.android.com/studio/debug/network-profiler",
5 | "tags": [
6 | "android-studio",
7 | "network",
8 | "debugging",
9 | "profiling"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/perf-sheet.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "perf-sheet",
3 | "description": "A tool that analyzes your method trace file and generates the analysis in a spreadsheet",
4 | "link": "https://github.com/theapache64/perf-sheet",
5 | "tags": [
6 | "analyzes",
7 | "profiling"
8 | ],
9 | "authorName": "theapache64",
10 | "authorLink": "https://github.com/theapache64"
11 | }
--------------------------------------------------------------------------------
/data/perfetto.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "perfetto",
3 | "description": "perfetto is a tool that lets you collect performance information from Android devices via the Android Debug Bridge (ADB).",
4 | "link": "https://developer.android.com/tools/perfetto",
5 | "tags": [
6 | "command-line",
7 | "performance",
8 | "profiling",
9 | "analysis"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/preview-animation.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Preview Animation",
3 | "description": "Inspect, debug, and preview animations frame by frame.",
4 | "link": "https://developer.android.com/develop/ui/compose/tooling/animation-preview",
5 | "tags": [
6 | "jetpack-compose",
7 | "animation",
8 | "preview",
9 | "debugging"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/preview-ui.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Preview UI",
3 | "description": "Preview, organize, and interact with composables.",
4 | "link": "https://developer.android.com/develop/ui/compose/tooling/previews",
5 | "tags": [
6 | "jetpack-compose",
7 | "ui",
8 | "preview",
9 | "development"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/r8-retrace.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "R8 retrace",
3 | "description": "R8 retrace is a tool for obtaining the original stack trace from an obfuscated stack trace. The stack trace is reconstructed by matching class and method names in a mapping file to their original definitions.",
4 | "link": "https://developer.android.com/tools/retrace",
5 | "tags": [
6 | "command-line",
7 | "obfuscation",
8 | "debugging",
9 | "stack-trace"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/record-a-video.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Record A Video",
3 | "description": "Logcat lets you record an MP4 video from your device, such as for marketing materials or for debugging. Videos are a maximum of three minutes long, and audio is not recorded with the video file.",
4 | "link": "https://developer.android.com/studio/debug/am-video",
5 | "tags": [
6 | "android-studio",
7 | "video",
8 | "debugging",
9 | "ui"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/relay-design-to-code.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Relay (Design to code)",
3 | "description": "Provide instant handoff of Android UI components between designers and developers. Designers can package up UI Components with information about layout, styling, dynamic content and interaction behavior. Developers can import those packages and convert them to Jetpack Compose code.",
4 | "link": "https://developer.android.com/develop/ui/compose/tooling/relay",
5 | "tags": [
6 | "jetpack-compose",
7 | "design",
8 | "ui",
9 | "collaboration"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/sdkmanager.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sdkmanager",
3 | "description": "The sdkmanager is a command-line tool that lets you view, install, update, and uninstall packages for the Android SDK. If you're using Android Studio, then you don't need to use this tool, and you can instead manage your SDK packages from the IDE.",
4 | "link": "https://developer.android.com/tools/sdkmanager",
5 | "tags": [
6 | "command-line",
7 | "sdk",
8 | "package-management",
9 | "installation"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/sqlite3.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sqlite3",
3 | "description": "From a remote shell to your device or from your host machine, use the sqlite3 command-line program to manage SQLite databases created by Android applications. The sqlite3 tool includes many useful commands, such as .dump to print out the contents of a table and .schema to print the SQL CREATE statement for an existing table. The tool also gives you the ability to execute SQLite commands on the fly.",
4 | "link": "https://developer.android.com/tools/sqlite3",
5 | "tags": [
6 | "command-line",
7 | "database",
8 | "sqlite",
9 | "data-management"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/take-a-screenshot.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Take A Screenshot",
3 | "description": "On many Android devices, you can capture a screenshot by pressing the Power and Volume-down buttons on the device simultaneously. To save a screenshot directly to your workstation, you can capture the screenshot using Android Studio.",
4 | "link": "https://developer.android.com/studio/debug/am-screenshot",
5 | "tags": [
6 | "android-studio",
7 | "screenshot",
8 | "debugging",
9 | "ui"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/toolargetool.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "toolargetool",
3 | "description": "A tool for debugging TransactionTooLargeException on Android",
4 | "link": "https://github.com/guardian/toolargetool",
5 | "tags": [
6 | "debugging"
7 | ],
8 | "authorName": "guardian",
9 | "authorLink": "https://github.com/guardian"
10 | }
--------------------------------------------------------------------------------
/data/workmanager-worker-inspector.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "WorkManager Worker Inspector",
3 | "description": "The Background Task Inspector helps you visualize, monitor, and debug your app's background workers when using WorkManager library 2.5.0 or higher.",
4 | "link": "https://developer.android.com/studio/inspect/task",
5 | "tags": [
6 | "android-studio",
7 | "background-tasks",
8 | "workmanager",
9 | "debugging"
10 | ]
11 | }
--------------------------------------------------------------------------------
/data/zipalign.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zipalign",
3 | "description": "zipalign is a zip archive alignment tool that helps ensure that all uncompressed files in the archive are aligned relative to the start of the file. This lets the files be accessed directly via mmap(2) , removing the need to copy this data in RAM and reducing your app's memory usage.",
4 | "link": "https://developer.android.com/tools/zipalign",
5 | "tags": [
6 | "command-line",
7 | "apk",
8 | "optimization",
9 | "memory"
10 | ]
11 | }
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import globals from 'globals'
3 | import reactHooks from 'eslint-plugin-react-hooks'
4 | import reactRefresh from 'eslint-plugin-react-refresh'
5 | import tseslint from 'typescript-eslint'
6 |
7 | export default tseslint.config(
8 | { ignores: ['dist'] },
9 | {
10 | extends: [js.configs.recommended, ...tseslint.configs.recommended],
11 | files: ['**/*.{ts,tsx}'],
12 | languageOptions: {
13 | ecmaVersion: 2020,
14 | globals: globals.browser,
15 | },
16 | plugins: {
17 | 'react-hooks': reactHooks,
18 | 'react-refresh': reactRefresh,
19 | },
20 | rules: {
21 | ...reactHooks.configs.recommended.rules,
22 | 'react-refresh/only-export-components': [
23 | 'warn',
24 | { allowConstantExport: true },
25 | ],
26 | },
27 | },
28 | )
29 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Awesome Android Tooling | A Curated Collection of Android Development Tools
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "awesome-android-tooling",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "node scripts/merge.js && vite",
8 | "build": "node scripts/merge.js && tsc -b && vite build",
9 | "lint": "eslint .",
10 | "preview": "vite preview",
11 | "validate": "node scripts/validate.js",
12 | "update-authors": "node scripts/update-authors.js"
13 | },
14 | "dependencies": {
15 | "@tailwindcss/vite": "^4.1.5",
16 | "class-variance-authority": "^0.7.1",
17 | "clsx": "^2.1.1",
18 | "lucide-react": "^0.507.0",
19 | "node-fetch": "^3.3.2",
20 | "posthog-js": "^1.239.1",
21 | "react": "^19.0.0",
22 | "react-dom": "^19.0.0",
23 | "tailwind-merge": "^3.2.0",
24 | "tailwindcss": "^4.1.5"
25 | },
26 | "devDependencies": {
27 | "@eslint/js": "^9.22.0",
28 | "@types/node": "^22.15.3",
29 | "@types/react": "^19.0.10",
30 | "@types/react-dom": "^19.0.4",
31 | "@vitejs/plugin-react-swc": "^3.8.0",
32 | "ajv": "^8.12.0",
33 | "ajv-formats": "^2.1.1",
34 | "autoprefixer": "^10.4.21",
35 | "eslint": "^9.22.0",
36 | "eslint-plugin-react-hooks": "^5.2.0",
37 | "eslint-plugin-react-refresh": "^0.4.19",
38 | "globals": "^16.0.0",
39 | "postcss": "^8.5.3",
40 | "tw-animate-css": "^1.2.9",
41 | "typescript": "~5.7.2",
42 | "typescript-eslint": "^8.26.1",
43 | "vite": "^6.3.1"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Redirecting...
8 |
27 |
28 |
29 |
Redirecting to the home page...
30 |
31 |
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yogeshpaliyal/awesome-android-tooling/890a7f7675792b0a16a52b19b6ab383220d536b3/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yogeshpaliyal/awesome-android-tooling/890a7f7675792b0a16a52b19b6ab383220d536b3/public/favicon-96x96.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yogeshpaliyal/awesome-android-tooling/890a7f7675792b0a16a52b19b6ab383220d536b3/public/favicon.ico
--------------------------------------------------------------------------------
/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "MyWebSite",
3 | "short_name": "MySite",
4 | "icons": [
5 | {
6 | "src": "/web-app-manifest-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png",
9 | "purpose": "maskable"
10 | },
11 | {
12 | "src": "/web-app-manifest-512x512.png",
13 | "sizes": "512x512",
14 | "type": "image/png",
15 | "purpose": "maskable"
16 | }
17 | ],
18 | "theme_color": "#ffffff",
19 | "background_color": "#ffffff",
20 | "display": "standalone"
21 | }
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/web-app-manifest-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yogeshpaliyal/awesome-android-tooling/890a7f7675792b0a16a52b19b6ab383220d536b3/public/web-app-manifest-192x192.png
--------------------------------------------------------------------------------
/public/web-app-manifest-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yogeshpaliyal/awesome-android-tooling/890a7f7675792b0a16a52b19b6ab383220d536b3/public/web-app-manifest-512x512.png
--------------------------------------------------------------------------------
/scripts/breakdown.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import { fileURLToPath } from 'url';
4 |
5 | // Get directory name in ES modules
6 | const __filename = fileURLToPath(import.meta.url);
7 | const __dirname = path.dirname(__filename);
8 |
9 | // Define paths
10 | const rootDir = path.resolve(__dirname, '..');
11 | const dataJsonPath = path.join(rootDir, 'src', 'data.json');
12 | const outputDir = path.join(rootDir, 'data');
13 |
14 | // Create data directory if it doesn't exist
15 | if (!fs.existsSync(outputDir)) {
16 | fs.mkdirSync(outputDir, { recursive: true });
17 | console.log(`Created directory: ${outputDir}`);
18 | }
19 |
20 | // Read the data.json file
21 | const rawData = fs.readFileSync(dataJsonPath, 'utf8');
22 | // Remove comment line if present
23 | const cleanData = rawData.replace(/^\s*\/\/.*$/m, '');
24 | const dataJson = JSON.parse(cleanData);
25 | const tools = dataJson.tools;
26 |
27 | // Create a file for each tool
28 | tools.forEach((tool, index) => {
29 | // Create a filename based on the tool name
30 | const fileName = tool.name
31 | .toLowerCase()
32 | .replace(/[^a-z0-9]+/g, '-') // Replace non-alphanumeric chars with hyphen
33 | .replace(/-+/g, '-') // Replace multiple hyphens with a single one
34 | .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
35 |
36 | // Create the file path
37 | const filePath = path.join(outputDir, `${fileName}.json`);
38 |
39 | // Write the individual tool to a JSON file
40 | fs.writeFileSync(filePath, JSON.stringify(tool, null, 2));
41 |
42 | console.log(`Created: ${filePath}`);
43 | });
44 |
45 |
46 | console.log(`\nTotal files created: ${tools.length + 1}`);
47 |
--------------------------------------------------------------------------------
/scripts/merge.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import { fileURLToPath } from 'url';
4 | import fetch from 'node-fetch';
5 |
6 | // Get directory name in ES modules
7 | const __filename = fileURLToPath(import.meta.url);
8 | const __dirname = path.dirname(__filename);
9 |
10 | // Define paths
11 | const rootDir = path.resolve(__dirname, '..');
12 | const dataDir = path.join(rootDir, 'data');
13 | const outputFile = path.join(rootDir, 'src', 'data.json');
14 |
15 | // Helper function to extract GitHub info from URL
16 | function extractGitHubInfo(url) {
17 | if (!url || !url.includes('github.com')) return null;
18 |
19 | try {
20 | const urlObj = new URL(url);
21 | if (urlObj.hostname !== 'github.com') return null;
22 |
23 | const path = urlObj.pathname.replace(/^\/|\/$/g, '');
24 | const [owner, repo] = path.split('/');
25 |
26 | if (!owner || !repo) return null;
27 |
28 | return { owner, repo };
29 | } catch (e) {
30 | return null;
31 | }
32 | }
33 |
34 | // Function to fetch GitHub repository data
35 | export async function fetchGitHubRepoData(url) {
36 | try {
37 | const repoInfo = extractGitHubInfo(url);
38 | if (!repoInfo) return null;
39 |
40 | const { owner, repo } = repoInfo;
41 |
42 | // Add a small random delay to avoid hitting rate limits
43 | await new Promise(resolve => setTimeout(resolve, 500 + Math.random() * 500));
44 |
45 | console.log(`Fetching GitHub data for ${owner}/${repo}...`);
46 | const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`);
47 |
48 | if (!response.ok) {
49 | if (response.status === 403) {
50 | console.warn('GitHub API rate limit exceeded');
51 | }
52 | console.error(`Error fetching data for ${owner}/${repo}: HTTP ${response.status}`);
53 | return null;
54 | }
55 |
56 | const data = await response.json();
57 |
58 | return {
59 | authorName: data.owner?.login || owner,
60 | authorLink: data.owner?.html_url || `https://github.com/${owner}`,
61 | stars: data.stargazers_count || 0,
62 | forks: data.forks_count || 0
63 | };
64 | } catch (error) {
65 | console.error('Error fetching GitHub repo data:', error);
66 | return null;
67 | }
68 | }
69 |
70 | // Read the index.json file to get all tool files
71 | console.log('Reading index.json...');
72 | const indexPath = path.join(dataDir, 'index.json');
73 | const indexExists = fs.existsSync(indexPath);
74 |
75 | let toolFiles = [];
76 |
77 | if (indexExists) {
78 | // Use index.json as a guide if it exists
79 | const indexData = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
80 | toolFiles = indexData.tools.map(tool => tool.file);
81 | console.log(`Found ${toolFiles.length} tools in index.json`);
82 | } else {
83 | // Otherwise read all .json files in the directory (except index.json)
84 | console.log('Index file not found, reading all JSON files in the directory...');
85 | toolFiles = fs.readdirSync(dataDir)
86 | .filter(file => file.endsWith('.json') && file !== 'index.json' && file !== '_template.json');
87 | console.log(`Found ${toolFiles.length} JSON files in directory`);
88 | }
89 |
90 | // Read each tool file and combine them
91 | const tools = [];
92 | for (const file of toolFiles) {
93 | try {
94 | const filePath = path.join(dataDir, file);
95 | const fileData = fs.readFileSync(filePath, 'utf8');
96 | const toolData = JSON.parse(fileData);
97 | tools.push(toolData);
98 | console.log(`Added: ${toolData.name}`);
99 | } catch (error) {
100 | console.error(`Error reading file ${file}:`, error.message);
101 | }
102 | }
103 |
104 | // Fetch GitHub data for tools that don't have author information
105 | async function enrichToolsWithGitHubData() {
106 | console.log('\nEnriching tools with GitHub data...');
107 |
108 | for (let i = 0; i < tools.length; i++) {
109 | const tool = tools[i];
110 |
111 | // Skip if both authorName and authorLink are already provided
112 | if (tool.authorName && tool.authorLink) {
113 | console.log(`${tool.name}: Author info already present (${tool.authorName})`);
114 | continue;
115 | }
116 |
117 | // Check if tool URL is from GitHub
118 | if (tool.link && tool.link.includes('github.com')) {
119 | console.log(`${tool.name}: Fetching author data from GitHub...`);
120 | const gitHubData = await fetchGitHubRepoData(tool.link);
121 |
122 | if (gitHubData) {
123 | // Add author information to the tool
124 | tool.authorName = tool.authorName || gitHubData.authorName;
125 | tool.authorLink = tool.authorLink || gitHubData.authorLink;
126 | console.log(`${tool.name}: Added author info - ${tool.authorName}`);
127 | } else {
128 | console.log(`${tool.name}: Unable to fetch GitHub data`);
129 | }
130 | } else {
131 | console.log(`${tool.name}: Not a GitHub URL, skipping`);
132 | }
133 | }
134 | }
135 |
136 | // Sort tools alphabetically by name
137 | tools.sort((a, b) => a.name.localeCompare(b.name));
138 |
139 | // Run async function to fetch GitHub data and then save combined data
140 | (async function() {
141 | try {
142 | await enrichToolsWithGitHubData();
143 |
144 | // Create the final data structure
145 | const finalData = {
146 | tools: tools
147 | };
148 |
149 | // Write the combined data to src/data.json
150 | fs.writeFileSync(outputFile, JSON.stringify(finalData, null, 2));
151 | console.log(`\nCreated combined file: ${outputFile}`);
152 | console.log(`Total tools merged: ${tools.length}`);
153 | } catch (error) {
154 | console.error('Error during GitHub data enrichment:', error);
155 | }
156 | })();
--------------------------------------------------------------------------------
/scripts/update-authors.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import { fileURLToPath } from 'url';
4 | import { fetchGitHubRepoData } from './merge.js'; // Import the function to fetch GitHub data
5 |
6 | // Get directory name in ES modules
7 | const __filename = fileURLToPath(import.meta.url);
8 | const __dirname = path.dirname(__filename);
9 |
10 | // Define paths
11 | const rootDir = path.resolve(__dirname, '..');
12 | const dataDir = path.join(rootDir, 'data');
13 |
14 | // Get all JSON files in the data directory (excluding index.json and _template.json)
15 | const jsonFiles = fs.readdirSync(dataDir)
16 | .filter(file => file.endsWith('.json') && file !== 'index.json' && file !== '_template.json');
17 |
18 | console.log(`Found ${jsonFiles.length} JSON files in the data directory`);
19 |
20 | // Function to update a single JSON file with author information
21 | async function updateFileWithAuthorInfo(filePath) {
22 | try {
23 | // Read and parse the file
24 | const fileContent = fs.readFileSync(filePath, 'utf8');
25 | const data = JSON.parse(fileContent);
26 | const fileName = path.basename(filePath);
27 |
28 | // Skip if both authorName and authorLink are already provided
29 | if (data.authorName && data.authorLink) {
30 | console.log(`${data.name}: Author info already present (${data.authorName})`);
31 | return false; // No update needed
32 | }
33 |
34 | // Check if tool URL is from GitHub
35 | if (data.link && data.link.includes('github.com')) {
36 | console.log(`${data.name}: Fetching author data from GitHub...`);
37 | const gitHubData = await fetchGitHubRepoData(data.link);
38 |
39 | if (gitHubData) {
40 | // Add author information to the tool
41 | data.authorName = data.authorName || gitHubData.authorName;
42 | data.authorLink = data.authorLink || gitHubData.authorLink;
43 |
44 | // Write the updated data back to the file
45 | fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
46 |
47 | console.log(`${data.name}: Updated with author info - ${data.authorName}`);
48 | return true; // Update successful
49 | } else {
50 | console.log(`${data.name}: Unable to fetch GitHub data`);
51 | }
52 | } else {
53 | console.log(`${data.name}: Not a GitHub URL, skipping`);
54 | }
55 |
56 | return false; // No update made
57 | } catch (error) {
58 | console.error(`Error updating file ${filePath}:`, error);
59 | return false;
60 | }
61 | }
62 |
63 | // Process all JSON files
64 | async function updateAllFiles() {
65 | console.log('\nUpdating JSON files with missing author information...');
66 |
67 | let updatedCount = 0;
68 | let notUpdatedCount = 0;
69 |
70 | for (const file of jsonFiles) {
71 | const filePath = path.join(dataDir, file);
72 | const updated = await updateFileWithAuthorInfo(filePath);
73 |
74 | if (updated) {
75 | updatedCount++;
76 | } else {
77 | notUpdatedCount++;
78 | }
79 | }
80 |
81 | console.log('\nUpdate Summary:');
82 | console.log(`Files updated: ${updatedCount}`);
83 | console.log(`Files not needing updates: ${notUpdatedCount}`);
84 | console.log(`Total files processed: ${jsonFiles.length}`);
85 | }
86 |
87 | // Run the update function
88 | updateAllFiles().catch(error => {
89 | console.error('Error during update process:', error);
90 | process.exit(1);
91 | });
--------------------------------------------------------------------------------
/scripts/validate.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import { fileURLToPath } from 'url';
4 | import Ajv from 'ajv';
5 | import addFormats from 'ajv-formats';
6 |
7 | // Get directory name in ES modules
8 | const __filename = fileURLToPath(import.meta.url);
9 | const __dirname = path.dirname(__filename);
10 |
11 | // Define paths
12 | const rootDir = path.resolve(__dirname, '..');
13 | const dataDir = path.join(rootDir, 'data');
14 |
15 | // JSON Schema for tool validation
16 | const toolSchema = {
17 | type: 'object',
18 | required: ['name', 'description', 'link', 'tags'],
19 | properties: {
20 | name: { type: 'string', minLength: 1 },
21 | description: { type: 'string', minLength: 10 },
22 | link: {
23 | type: 'string',
24 | // Use pattern instead of format for URL validation
25 | pattern: '^https?://[\\w.-]+(\\.[\\w.-]+)+([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?$'
26 | },
27 | authorName: { type: 'string' },
28 | authorLink: {
29 | type: 'string',
30 | pattern: '^https?://[\\w.-]+(\\.[\\w.-]+)+([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?$'
31 | },
32 | tags: {
33 | type: 'array',
34 | minItems: 1,
35 | items: { type: 'string', minLength: 1 }
36 | }
37 | },
38 | additionalProperties: false
39 | };
40 |
41 | // Initialize Ajv
42 | const ajv = new Ajv({ allErrors: true });
43 | // Add format support (optional, since we're using pattern)
44 | addFormats(ajv);
45 | const validate = ajv.compile(toolSchema);
46 |
47 | // Function to validate a JSON file
48 | function validateJsonFile(filePath) {
49 | try {
50 | // Read and parse the file
51 | const fileContent = fs.readFileSync(filePath, 'utf8');
52 | const data = JSON.parse(fileContent);
53 |
54 | // Basic URL validation (as a fallback)
55 | if (!data.link.startsWith('http://') && !data.link.startsWith('https://')) {
56 | console.error(`\x1b[31m❌ Validation failed for ${path.basename(filePath)}:\x1b[0m`);
57 | console.error(` - /link must start with http:// or https://`);
58 | return false;
59 | }
60 |
61 | // Validate against schema
62 | const valid = validate(data);
63 |
64 | if (!valid) {
65 | console.error(`\x1b[31m❌ Validation failed for ${path.basename(filePath)}:\x1b[0m`);
66 | validate.errors.forEach(error => {
67 | console.error(` - ${error.instancePath} ${error.message}`);
68 | });
69 | return false;
70 | }
71 |
72 | return true;
73 | } catch (error) {
74 | console.error(`\x1b[31m❌ Error processing ${path.basename(filePath)}: ${error.message}\x1b[0m`);
75 | return false;
76 | }
77 | }
78 |
79 | // Main validation function
80 | async function validateAllFiles() {
81 | // Check if data directory exists
82 | if (!fs.existsSync(dataDir)) {
83 | console.error('\x1b[31m❌ Data directory not found!\x1b[0m');
84 | process.exit(1);
85 | }
86 |
87 | // Get all JSON files except index.json
88 | const files = fs.readdirSync(dataDir)
89 | .filter(file => file.endsWith('.json') && file !== 'index.json' && file !== '_template.json')
90 | .map(file => path.join(dataDir, file));
91 |
92 | console.log(`\x1b[36mValidating ${files.length} JSON files in data directory...\x1b[0m\n`);
93 |
94 | // Validate each file
95 | let validCount = 0;
96 | let invalidCount = 0;
97 |
98 | for (const file of files) {
99 | const isValid = validateJsonFile(file);
100 | if (isValid) {
101 | validCount++;
102 | console.log(`\x1b[32m✅ ${path.basename(file)} is valid\x1b[0m`);
103 | } else {
104 | invalidCount++;
105 | }
106 | }
107 |
108 | // Special validation for index.json if it exists
109 | const indexPath = path.join(dataDir, 'index.json');
110 | if (fs.existsSync(indexPath)) {
111 | try {
112 | const indexContent = fs.readFileSync(indexPath, 'utf8');
113 | JSON.parse(indexContent); // Just check if it's valid JSON
114 | console.log('\x1b[32m✅ index.json is valid JSON\x1b[0m');
115 | } catch (error) {
116 | console.error(`\x1b[31m❌ Error with index.json: ${error.message}\x1b[0m`);
117 | invalidCount++;
118 | }
119 | }
120 |
121 | // Summary
122 | console.log('\n\x1b[36mValidation Summary:\x1b[0m');
123 | console.log(`\x1b[32m✅ Valid files: ${validCount}\x1b[0m`);
124 | if (invalidCount > 0) {
125 | console.log(`\x1b[31m❌ Invalid files: ${invalidCount}\x1b[0m`);
126 | process.exit(1); // Exit with error code
127 | } else {
128 | console.log('\x1b[32m✅ All JSON files are valid!\x1b[0m');
129 | }
130 | }
131 |
132 | // Run validation
133 | validateAllFiles().catch(error => {
134 | console.error(error);
135 | process.exit(1);
136 | });
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | /* This file is kept minimal as we're using Tailwind CSS for styling */
2 |
3 | /* Only keeping transitions for dark mode that aren't easily handled by Tailwind */
4 | .transition-mode {
5 | transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
6 | }
7 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from 'react'
2 | import './App.css'
3 | import { SearchBar } from './components/ui/search-bar'
4 | import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './components/ui/card'
5 | import { Footer } from './components/ui/footer'
6 | import { Pagination } from './components/ui/pagination'
7 | import { MasonryGrid } from './components/ui/masonry-grid'
8 | import { Header } from './components/ui/header'
9 | import { GitHubStats } from './components/ui/github-stats'
10 | import { FloatingActionButton } from './components/ui/floating-action-button'
11 | import { ToolOfTheDay } from './components/ui/tool-of-the-day'
12 | import { getToolOfTheDay } from './lib/utils'
13 |
14 | // Define type for the tools
15 | interface Tool {
16 | name: string;
17 | description: string;
18 | link: string;
19 | tags: string[];
20 | author?: string; // Optional author field
21 | authorName?: string; // GitHub author name
22 | authorLink?: string; // GitHub author profile link
23 | }
24 |
25 | interface ToolsData {
26 | tools: Tool[];
27 | }
28 |
29 | // Array of pastel background colors for cards
30 | const pastelColors = [
31 | 'bg-card-pastel-green',
32 | 'bg-card-pastel-blue',
33 | 'bg-card-pastel-yellow',
34 | 'bg-card-pastel-purple',
35 | 'bg-card-pastel-red',
36 | 'bg-card-pastel-indigo',
37 | 'bg-card-pastel-cyan',
38 | 'bg-card-pastel-teal',
39 | 'bg-card-pastel-lime',
40 | 'bg-card-pastel-orange',
41 | ];
42 |
43 | // Function to get a random pastel color
44 | const getRandomPastelColor = () => {
45 | const randomIndex = Math.floor(Math.random() * pastelColors.length);
46 | return pastelColors[randomIndex];
47 | };
48 |
49 | // Function to update URL query parameters
50 | const updateUrlWithTag = (tag: string | null) => {
51 | const url = new URL(window.location.href);
52 |
53 | if (tag) {
54 | url.searchParams.set('tag', tag);
55 | } else {
56 | url.searchParams.delete('tag');
57 | }
58 |
59 | window.history.pushState({}, '', url);
60 | };
61 |
62 | // Function to get tag from URL query parameters
63 | const getTagFromUrl = (): string | null => {
64 | const params = new URLSearchParams(window.location.search);
65 | return params.get('tag');
66 | };
67 |
68 | function App() {
69 | const [tools, setTools] = useState([]);
70 | const [searchTerm, setSearchTerm] = useState('');
71 | const [selectedTag, setSelectedTag] = useState(null);
72 | const [allTags, setAllTags] = useState([]);
73 | const [popularTags, setPopularTags] = useState([]);
74 | const [darkMode, setDarkMode] = useState(false);
75 | const [scrolled, setScrolled] = useState(false);
76 | const [currentPage, setCurrentPage] = useState(1);
77 | const [itemsPerPage] = useState(15); // Show 9 tools per page (3x3 grid)
78 | const [heroVisible, setHeroVisible] = useState(true);
79 | const [toolOfTheDayOpen, setToolOfTheDayOpen] = useState(false);
80 | const [toolOfTheDay, setToolOfTheDay] = useState(null);
81 | const heroRef = useRef(null);
82 |
83 | // Store random colors for each tool to ensure consistent colors between renders
84 | const [cardColors, setCardColors] = useState<{[key: string]: string}>({});
85 |
86 | // Function to select the Tool of the Day using the date-based algorithm
87 | const selectToolOfTheDay = () => {
88 | const todaysTool = getToolOfTheDay(tools);
89 | if (todaysTool) {
90 | setToolOfTheDay(todaysTool);
91 | setToolOfTheDayOpen(true);
92 | }
93 | };
94 |
95 | // Use useEffect to initialize the Tool of the Day when the app loads
96 | useEffect(() => {
97 | if (tools.length > 0) {
98 | const todaysTool = getToolOfTheDay(tools);
99 | if (todaysTool) {
100 | setToolOfTheDay(todaysTool);
101 | // Don't automatically open the modal, just set the tool
102 | }
103 | }
104 | }, [tools]);
105 |
106 | // Load data from data.json
107 | useEffect(() => {
108 | const fetchData = async () => {
109 | try {
110 | // Import the data directly using Vite's JSON import
111 | const data = (await import('./data.json')).default as ToolsData;
112 | setTools(data.tools);
113 |
114 | // Extract unique tags for filtering
115 | const tags = new Set();
116 | const tagCount: {[key: string]: number} = {};
117 |
118 | data.tools.forEach(tool => {
119 | tool.tags.forEach(tag => {
120 | tags.add(tag);
121 | tagCount[tag] = (tagCount[tag] || 0) + 1;
122 | });
123 | });
124 |
125 | // Sort all tags alphabetically
126 | setAllTags(Array.from(tags).sort());
127 |
128 | // Determine popular tags (top 6 most used)
129 | const popular = Object.entries(tagCount)
130 | .sort((a, b) => b[1] - a[1])
131 | .slice(0, 6)
132 | .map(entry => entry[0]);
133 |
134 | setPopularTags(popular);
135 |
136 | // Check for tag in URL
137 | const urlTag = getTagFromUrl();
138 | if (urlTag && Array.from(tags).includes(urlTag)) {
139 | setSelectedTag(urlTag);
140 | }
141 | } catch (error) {
142 | console.error('Error loading data:', error);
143 | }
144 | };
145 |
146 | fetchData();
147 | }, []);
148 |
149 | // Generate random colors for each tool once when tools are loaded
150 | useEffect(() => {
151 | if (tools.length > 0 && Object.keys(cardColors).length === 0) {
152 | const colors: {[key: string]: string} = {};
153 | tools.forEach(tool => {
154 | colors[tool.name] = getRandomPastelColor();
155 | });
156 | setCardColors(colors);
157 | }
158 | }, [tools, cardColors]);
159 |
160 | // Handle scroll effects for navbar, hero visibility and dark mode preference
161 | useEffect(() => {
162 | const handleScroll = () => {
163 | setScrolled(window.scrollY > 80);
164 |
165 | // Check if hero section is still visible
166 | if (heroRef.current) {
167 | const heroRect = heroRef.current.getBoundingClientRect();
168 | // Hero is considered not visible when it's top edge is above the viewport
169 | // Add some threshold for a smoother transition
170 | setHeroVisible(heroRect.bottom > 100);
171 | }
172 | };
173 |
174 | window.addEventListener('scroll', handleScroll);
175 |
176 | // Check user's preferred color scheme
177 | const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
178 | setDarkMode(prefersDark);
179 |
180 | // Apply dark mode class to html element (for Tailwind)
181 | if (prefersDark) {
182 | document.documentElement.classList.add('dark');
183 | } else {
184 | document.documentElement.classList.remove('dark');
185 | }
186 |
187 | return () => {
188 | window.removeEventListener('scroll', handleScroll);
189 | };
190 | }, []);
191 |
192 | // Toggle dark mode
193 | const toggleDarkMode = () => {
194 | setDarkMode(prev => {
195 | const newMode = !prev;
196 | if (newMode) {
197 | document.documentElement.classList.add('dark');
198 | } else {
199 | document.documentElement.classList.remove('dark');
200 | }
201 | return newMode;
202 | });
203 | };
204 |
205 | // Filter tools based on search term and selected tag
206 | const filteredTools = tools.filter(tool => {
207 | const matchesSearch =
208 | searchTerm === '' ||
209 | tool.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
210 | tool.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
211 | tool.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()));
212 |
213 | const matchesTag = selectedTag === null || tool.tags.includes(selectedTag);
214 |
215 | return matchesSearch && matchesTag;
216 | });
217 |
218 | // Handle search input change
219 | const handleSearchChange = (e: React.ChangeEvent) => {
220 | setSearchTerm(e.target.value);
221 | setCurrentPage(1); // Reset to first page on new search
222 | };
223 |
224 | // Handle tag selection with URL update
225 | const handleTagSelect = (tag: string) => {
226 | const newTag = selectedTag === tag ? null : tag;
227 | setSelectedTag(newTag);
228 | updateUrlWithTag(newTag);
229 | setCurrentPage(1); // Reset to first page on tag change
230 | };
231 |
232 | // Clear all filters with URL update
233 | const clearFilters = () => {
234 | setSelectedTag(null);
235 | updateUrlWithTag(null);
236 | setSearchTerm('');
237 | setCurrentPage(1);
238 | };
239 |
240 | // Pagination logic
241 | const totalPages = Math.ceil(filteredTools.length / itemsPerPage);
242 | const indexOfLastItem = currentPage * itemsPerPage;
243 | const indexOfFirstItem = indexOfLastItem - itemsPerPage;
244 | const currentTools = filteredTools.slice(indexOfFirstItem, indexOfLastItem);
245 |
246 | // Handle URL changes from browser back/forward buttons
247 | useEffect(() => {
248 | const handlePopState = () => {
249 | const urlTag = getTagFromUrl();
250 | setSelectedTag(urlTag);
251 | };
252 |
253 | window.addEventListener('popstate', handlePopState);
254 | return () => {
255 | window.removeEventListener('popstate', handlePopState);
256 | };
257 | }, []);
258 |
259 | // Handle page change
260 | const handlePageChange = (pageNumber: number) => {
261 | setCurrentPage(pageNumber);
262 | // Scroll to top of tools section
263 | window.scrollTo({
264 | top: document.querySelector('.tools-section')?.getBoundingClientRect().top! + window.scrollY - 100,
265 | behavior: 'smooth'
266 | });
267 | };
268 |
269 | return (
270 |
271 | {/* Use the new Header component with heroVisible prop */}
272 |
278 |
279 |
280 |
281 | {/**@ts-ignore */}
282 |
Awesome Android Tooling
283 |
284 | A curated list of tools that can be helpful building, testing, and optimizing your Android apps.
285 |
286 |
287 |
288 | {/* Container with fixed width to prevent layout shifts */}
289 |