Project Overview
A retro-styled Pokédex application built with JavaFX, featuring pixel-perfect UI positioning, JSON data loading, sound effects, and an authentic Pokémon lookup experience.
- Browse all 151 original Kanto Pokémon
- Reveal/Hide toggle for mystery mode
- Power on/off functionality
- Navigate with previous/next buttons
- Detailed Pokémon information display
- Silhouette effect for hidden Pokémon
- Authentic retro pixel art style
- "Who's That Pokemon?" sound effect
- MVC pattern implementation
- JavaFX custom UI design
- JSON data parsing with Jackson
- Resource management (images, fonts, audio)
- Event-driven programming
- State management patterns
- Java Module System (JPMS)
- Media playback with JavaFX
| Technology | Version | Purpose |
|---|---|---|
| Java | 21 | Core programming language |
| JavaFX | 21 | UI framework for desktop application |
| JavaFX Media | 21 | Audio playback for sound effects |
| Jackson | 2.15.2 | JSON parsing and data binding |
| Maven | 3.x | Build automation and dependency management |
Architecture
MVC Design Pattern
This application follows the Model-View-Controller architectural pattern, ensuring clean separation of concerns, maintainability, and testability. Each component has a distinct responsibility with well-defined interfaces between layers.
Package: com.example.model
File: PokedexModel.java
Responsibilities:
- Manages application state and business logic
- Loads and stores Pokémon data from JSON
- Handles navigation (next/previous)
- Manages reveal/hide state
- Tracks power on/off state
- Provides data access methods
- Wraps around boundaries (first ↔ last)
Key Methods:
loadData()- JSON parsingnextPokemon()- Navigate forwardpreviousPokemon()- Navigate backwardtoggleReveal()- Toggle display modegetCurrentPokemon()- Get active PokémonpowerOn() / powerOff()- State control
Package: com.example.view
File: PokedexView.java
Responsibilities:
- Renders the user interface
- Displays Pokémon information and sprites
- Creates interactive button hitboxes
- Applies visual effects (silhouettes)
- Manages layout and positioning
- Loads fonts and background images
- Plays sound effects
- Updates UI based on model state
UI Components:
- Background image (pokedex_bg.png)
- Pokémon sprite display (ImageView)
- Name, description, types labels
- Dex number display
- Interactive button rectangles
- Custom pixel font (Press Start 2P)
- MediaPlayer for sound effects
Package: com.example.controller
File: PokedexController.java
Responsibilities:
- Bridges Model and View
- Handles user input events
- Updates View when Model changes
- Coordinates application flow
- Enforces business rules (e.g., powered-on requirement)
- Manages button click handlers
- Loads Pokemon data at startup
Event Handlers:
handlePowerOn()- Activates the PokédexhandlePowerOff()- Deactivates the PokédexhandleNextPokemon()- Shows next Pokémon-
handlePreviousPokemon()- Shows previous Pokémon -
handleToggleReveal()- Toggles mystery mode updateDisplay()- Refreshes display
The application maintains several state variables in the Model:
| State Variable | Type | Purpose |
|---|---|---|
currentIndex |
int | Currently displayed Pokémon position |
isRevealed |
boolean | Whether Pokémon details are visible |
isPoweredOn |
boolean | Whether the Pokédex is active |
pokedexList |
List<Pokemon> | All loaded Pokémon data |
isPoweredOn == false. This
simulates a real device that must be powered on to function.
Core Components
Represents a single Pokémon with all its attributes. This POJO (Plain Old Java Object) is used by Jackson for JSON deserialization.
package com.example.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class Pokemon {
private String picture; // Filename of sprite image
private String name; // Pokémon name
private int evolutionStage; // 1, 2, or 3
private String shortDescription; // Brief description
private int dexNumber; // National Pokédex number
@JsonProperty("isLegendary")
private boolean legendary; // Legendary status
private List<String> types; // e.g., ["Fire", "Flying"]
// Getters and setters for all fields
}
@JsonProperty("isLegendary") annotation maps the JSON
field "isLegendary" to the boolean field "legendary". All other
fields use automatic mapping since they match JSON keys exactly.
The main class that extends Application and
initializes the MVC components. It sets up the Scene and Stage,
then delegates control to the Controller.
package com.example;
import com.example.controller.PokedexController;
import com.example.model.PokedexModel;
import com.example.view.PokedexView;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage stage) {
// Initialize MVC components
PokedexModel model = new PokedexModel();
PokedexView view = new PokedexView(getClass());
PokedexController controller =
new PokedexController(model, view, getClass());
// Load data
controller.loadData();
// Create scene
Scene scene = new Scene(
view.getRoot(),
PokedexView.getWindowWidth(),
PokedexView.getWindowHeight()
);
// Setup stage
stage.setTitle("Pokédex");
stage.setScene(scene);
stage.setResizable(false);
stage.show();
// Initialize to powered off state
controller.initializePoweredOff();
}
public static void main(String[] args) {
launch(args);
}
}
The application uses
Class.getResourceAsStream()
to load resources, ensuring they work correctly whether running
from IDE or JAR file. The Class object is passed to the View and
Controller for resource access.
// In View constructor - passed from App
public PokedexView(Class> resourceClass) {
this.resourceClass = resourceClass;
// ... initialization
}
// Loading an image
String imgPath = "/pokedex/pokemon-pictures/1.png";
InputStream stream = resourceClass.getResourceAsStream(imgPath);
if (stream != null) {
Image image = new Image(stream);
imageView.setImage(image);
}
// Loading font
InputStream fontStream = resourceClass
.getResourceAsStream("/fonts/PressStart2P-Regular.ttf");
Font customFont = Font.loadFont(fontStream, 12);
// Loading audio
String audioPath = resourceClass
.getResource("/whos-that-pokemon.mp3").toExternalForm();
Media media = new Media(audioPath);
MediaPlayer player = new MediaPlayer(media);
getResourceAsStream() returns null. Always
validate before using to avoid NullPointerException.
Jackson ObjectMapper handles the deserialization of JSON data into Java objects. The Model uses this to populate the Pokémon list at startup.
public boolean loadData(InputStream inputStream) {
ObjectMapper mapper = new ObjectMapper();
try {
if (inputStream == null) {
return false;
}
// Deserialize JSON array into List<Pokemon>
pokedexList = mapper.readValue(
inputStream,
new TypeReference<List<Pokemon>>() {}
);
return !pokedexList.isEmpty();
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
All UI elements use absolute positioning relative to the background image. Constants define each component's exact location to ensure pixel-perfect alignment.
// View class constants
private static final double WIN_W = 548;
private static final double WIN_H = 400;
private static final double SCREEN_X = 128, SCREEN_Y = 125;
private static final double SCREEN_W = 104, SCREEN_H = 104;
private static final double POKEMON_NUMBER_X = 79;
private static final double POKEMON_NUMBER_Y = 140;
private static final double DESC_X = 77, DESC_Y = 314;
private static final double DESC_W = 111, DESC_H = 43;
When in mystery mode (isRevealed = false), the
Pokémon sprite is shown as a black silhouette using JavaFX's
ColorAdjust effect.
// Create the effect (in View constructor)
silhouetteEffect = new ColorAdjust();
silhouetteEffect.setBrightness(-1.0); // Maximum darkness
// Apply to ImageView (in displayPokemonHidden)
if (!model.isRevealed()) {
mainScreenImage.setEffect(silhouetteEffect);
} else {
mainScreenImage.setEffect(null); // Clear effect
}
The application plays a "Who's That Pokemon?" sound effect when entering mystery mode. This enhances the authentic Pokemon experience.
// MediaPlayer field in View class
private MediaPlayer mediaPlayer;
// Play sound when displaying hidden Pokemon
private void playWhosThatPokemonSound() {
try {
// Stop existing sound if playing
if (mediaPlayer != null) {
mediaPlayer.stop();
}
// Load and play sound
String path = resourceClass
.getResource("/whos-that-pokemon.mp3")
.toExternalForm();
Media media = new Media(path);
mediaPlayer = new MediaPlayer(media);
mediaPlayer.play();
} catch (Exception e) {
System.err.println("Could not play sound: " + e.getMessage());
}
}
// Called from displayPokemonHidden method
public void displayPokemonHidden(Pokemon pokemon, boolean isPoweredOn) {
if (pokemon == null) return;
// Play sound effect
if (isPoweredOn) {
playWhosThatPokemonSound();
}
// Apply silhouette and update UI
// ...
}
requires javafx.media; to your module-info.java
file and include the javafx-media dependency in pom.xml.
Project Structure
-
📁 demo
-
📂 src
-
📁 main
-
📁 java
-
📁 com.example
-
J App.java
-
📁 controller
-
J PokedexController.java
-
-
📁 model
-
J PokedexModel.java
-
J Pokemon.java
-
-
📁 view
-
J PokedexView.java
-
-
-
J module-info.java
-
-
📁 resources
-
🖼 pokedex_bg.png
-
♪ whos-that-pokemon.mp3
-
📁 pokedex
-
{} kanto_pokemon.json
-
📁 pokemon-pictures
-
🖼 1.png
-
🖼 2.png
-
... (more sprites)
-
(151 Pokemon images total)
-
-
-
📁 fonts
-
🔤 PressStart2P-Regular.ttf
-
-
-
-
-
<> pom.xml
-
M README.md
-
-
Pokemon images are in
/pokedex/pokemon-pictures/subdirectory -
Background image is at root resources level:
/pokedex_bg.png -
Sound effect is at root resources level:
/whos-that-pokemon.mp3 - MVC packages are separated: controller, model, view
Prerequisites:
- Java 21 or higher (JDK)
- Maven 3.6 or higher
- Git (for cloning)
Step 1: Clone Repository
cd demo
Step 2: Build Project
Step 3: Run Application
module-info.java properly exports packages and opens
the model package to Jackson for reflection:
module com.example {
// JavaFX dependencies
requires javafx.controls;
requires javafx.graphics;
requires javafx.media; // Required for sound effects
requires javafx.fxml;
// Jackson dependencies
requires com.fasterxml.jackson.databind;
requires com.fasterxml.jackson.core;
requires com.fasterxml.jackson.annotation;
// Export packages
exports com.example;
exports com.example.controller;
exports com.example.view;
exports com.example.model;
// Open for reflection (Jackson)
opens com.example.model to com.fasterxml.jackson.databind;
}
Key sections of the Maven configuration:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>21</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>21</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>21</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<executions>
<execution>
<id>default-cli</id>
<configuration>
<mainClass>com.example.App</mainClass>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Data Structure
The Pokémon data is stored in a JSON array. Each entry follows this structure:
[
{
"picture": "pokemon-pictures/1.png",
"name": "bulbasaur",
"evolutionStage": 1,
"shortDescription": "A strange seed was planted on its back at birth...",
"dexNumber": 1,
"isLegendary": false,
"types": ["grass", "poison"]
},
{
"picture": "pokemon-pictures/2.png",
"name": "ivysaur",
"evolutionStage": 2,
"shortDescription": "When the bulb on its back grows large...",
"dexNumber": 2,
"isLegendary": false,
"types": ["grass", "poison"]
}
]
| Field | Type | Description |
|---|---|---|
picture |
String | Relative path to sprite (e.g., "pokemon-pictures/1.png") |
name |
String | Pokémon name (displayed prominently) |
evolutionStage |
Integer | Evolution stage: 1 (basic), 2 (stage 1), 3 (stage 2) |
shortDescription |
String | Brief description shown in the Pokédex screen |
dexNumber |
Integer | National Pokédex number (1-151 for Kanto) |
isLegendary |
Boolean | Whether the Pokémon is legendary |
types |
Array[String] | Pokémon types (e.g., ["fire"], ["water", "flying"]) |
- Picture field: Must be a relative path from /pokedex/ directory (includes "pokemon-pictures/" subdirectory)
- Evolution stage: Must be 1, 2, or 3 (no other values)
- Dex number: Should be unique and sequential for Kanto (1-151)
- Types: Can have 1 or 2 types (no Pokémon has zero types)
- Short description: Should be brief enough to fit in the display area
| Pokémon | Types | Type Combination |
|---|---|---|
| Bulbasaur | grass, poison | Dual-type |
| Charmander | fire | Single-type |
| Gyarados | water, flying | Dual-type |
| Zapdos | electric, flying | Dual-type, Legendary |
UI System
The UI uses pixel-perfect absolute positioning relative to the background image. Each component is carefully aligned to create an authentic retro Pokédex experience.
| Component | X, Y Position | Dimensions |
|---|---|---|
| Window | - | 548 × 400 px |
| Pokémon Screen | 128, 125 | 104 × 104 px |
| Dex Number | 79, 140 | Auto-sized |
| Description | 77, 314 | 111 × 43 px |
| Details Labels | 337, 128 (starts) | Auto-sized |
| Button | X, Y Position | Size (W×H) | Function |
|---|---|---|---|
| Reveal/Hide | 30, 272 | 37 × 37 px | Toggle Mode |
| Power On | 127.72, 14.34 | 16 × 16 px | Boot Device |
| Power Off | 84.65, 14.34 | 16 × 16 px | Shutdown |
| Navigate Left | 205, 309 | 17 × 17 px | Previous |
| Navigate Right | 244, 309 | 17 × 17 px | Next |
Button objects with opacity set to 0.
They are positioned precisely over the background button
graphics.
- Font: Press Start 2P - Authentic retro pixel font for that classic 8-bit feel
-
Text Colors:
- Standard text: #000000 (Black)
- Mystery mode: #C42020FF (Red)
- Description: #084036 (Dark green)
- Digital display: #32EE25 (Bright green)
-
Silhouette Effect:
ColorAdjusteffect with brightness set to -1.0 creates perfect black silhouettes - Text Wrapping: Description text wraps at 111px width to fit the screen area
- Font Sizes: Carefully calibrated for readability (6-12px depending on component)
The View class uses a Pane as the root container,
allowing absolute positioning of all children. Key implementation
points:
// Root container setup
root = new Pane();
// Background image
ImageView background = new ImageView(
new Image(resourceClass.getResourceAsStream("/pokedex_bg.png"))
);
background.setFitWidth(WIN_W);
background.setFitHeight(WIN_H);
root.getChildren().add(background);
// Positioned components
mainScreenImage.setLayoutX(SCREEN_X);
mainScreenImage.setLayoutY(SCREEN_Y);
mainScreenImage.setFitWidth(SCREEN_W);
mainScreenImage.setFitHeight(SCREEN_H);
pokemonNumberLabel.setLayoutX(POKEMON_NUMBER_X);
pokemonNumberLabel.setLayoutY(POKEMON_NUMBER_Y);
Current Implementation: Fixed window size (548×400) for pixel-perfect display matching the background image.
Extension Opportunity: For a responsive version, you could:
- Scale all coordinates proportionally to window size
- Use percentage-based positioning
- Implement SVG graphics instead of PNG for scaling
- Add multiple layout profiles for different screen sizes
Development Workflow
- Prepare the sprite image: Ensure it's a PNG file with transparent background, sized appropriately (square recommended)
-
Add image to resources: Place the file in
src/main/resources/pokedex/pokemon-pictures/with number as filename (e.g., "152.png") -
Update JSON data: Add a new entry to
kanto_pokemon.jsonwith all required fields -
Verify filename match: Ensure the
picturefield in JSON matches: "pokemon-pictures/152.png" -
Rebuild project: Run
mvn clean install - Test: Run the application and navigate to the new Pokémon to verify display
-
Locate constants: Open
PokedexView.javaand find the positioning constants at the top -
Adjust coordinates: Modify
SCREEN_X,SCREEN_Y, or other position/size constants as needed - Recompile and test: Run the application to verify positioning looks correct
- Update button hitboxes: If buttons no longer align with graphics, adjust their coordinates
- Consider background changes: If modifying the background image, update all dependent coordinates accordingly
Follow the MVC pattern:
-
Model: Add state variables and business logic
methods to
PokedexModel.java -
View: Add new UI components and positioning
in
PokedexView.java -
Controller: Create event handlers and wire up
interactions in
PokedexController.java - Test thoroughly: Verify all state transitions work correctly and UI updates properly
Example - Adding a "Favorite" feature:
-
Model: Add
Set<Integer> favoritesandtoggleFavorite()method - View: Add a star icon/button and highlight for favorites
-
Controller: Add
handleFavoriteToggle()handler and update view to show favorite status
Before considering a feature complete, verify the following:
| Category | Test Cases |
|---|---|
| Power State |
|
| Navigation |
|
| Display |
|
| Reveal/Hide |
|
| Sound |
|
| Data Integrity |
|
| Issue | Possible Causes | Solutions |
|---|---|---|
| Images not loading |
|
|
| JSON parsing error |
|
|
| Sound not playing |
|
|
| Font not displaying |
|
|
| Module errors |
|
|
| Buttons not working |
|
|
| UI misalignment |
|
|
- Console Logging: Add System.out.println() statements to track state changes and method calls
- Resource Loading: Always check if getResourceAsStream() returns null before using the resource
- State Inspection: Print Model state variables after each interaction to verify correct updates
- JSON Validation: Use online JSON validators (jsonlint.com) to catch syntax errors
- Visual Debugging: Temporarily make button hitboxes visible (set opacity to 0.3) to verify positioning
-
Maven Clean: When in doubt, run
mvn clean installto ensure fresh build - Sound Testing: Check console for MediaPlayer errors if sound doesn't play
Extension Ideas
This project provides an excellent foundation for expanding functionality. Here are some ideas for taking it further, organized by difficulty level and feature category.
- Search Functionality: Add a search bar to find Pokémon by name or type
- Favorite System: Allow users to mark and filter favorite Pokémon
- Evolution Tree: Visual display of evolution chains with arrows
- More Sound Effects: Add button click sounds, different cries for each Pokemon
- Animations: Smooth transitions when navigating between Pokémon
- Compare Mode: Side-by-side comparison of two Pokémon
- Type Effectiveness: Show type matchup chart for current Pokémon
- Multiple Regions: Support for Johto, Hoenn, Sinnoh, etc. with region selector
- Volume Control: Add slider to adjust sound effect volume
- Base Stats: Display HP, Attack, Defense, Special Attack, Special Defense, Speed
- Stat Visualization: Show stats as radar chart or bar graph
- Abilities: List Pokémon abilities with descriptions
- Move Sets: Show learnable moves and level requirements
- Evolution Requirements: Display level, stone, or trade requirements
- Catch Information: Include catch rate, locations, and encounter rates
- Alternate Forms: Support for Mega Evolutions, regional variants, etc.
- Pokédex Entries: Multiple versions from different games
- Unit Testing: Add JUnit tests for Model and Controller logic
- External API: Integrate with PokéAPI (pokeapi.co) for live data
- Data Caching: Cache API responses to reduce network requests
- User Preferences: Save/load settings and favorites to file
- Export Functionality: Export Pokémon data to CSV, PDF, or JSON
- Multi-language: Support for multiple languages (i18n)
- Responsive Layout: Dynamic sizing for different screen resolutions
- Offline Mode: Full functionality without internet connection
- Themes: Multiple color schemes (original, blue, purple, etc.)
- Animations: Fade transitions, slide effects, sprite animations
- Keyboard Navigation: Arrow keys and hotkeys for all functions
- Touch Support: Swipe gestures for navigation on tablets
- Accessibility: Screen reader support and high contrast mode
- Custom Backgrounds: Allow users to upload custom Pokédex skins
- Quiz Mode: "Who's that Pokémon?" guessing game (already has sound effect!)
- Collection Tracker: Mark which Pokémon you've "caught"
- Completion Statistics: Track percentage of Pokédex completed
- Daily Challenge: New Pokémon to identify each day
- Team Builder: Create and save battle teams
- Type Coverage: Analyze team type strengths and weaknesses
- Database: Store data in SQLite or H2 for better performance
- Cloud Sync: Sync favorites and progress across devices
- Social Features: Share favorite teams or collections
- Import from Games: Read save data from Pokémon game files
| Difficulty | Example Features | Estimated Time |
|---|---|---|
| Easy | Favorite marking, keyboard shortcuts, themes, additional data fields, volume control | 2-5 hours |
| Medium | Search functionality, stat visualization, more sound effects, animations, export to file | 1-3 days |
| Hard | API integration, database implementation, evolution tree visualization, quiz mode | 1-2 weeks |
| Advanced | Cloud sync, multi-user features, responsive layout, full accessibility | 2-4 weeks |
Dependencies
| Dependency | Version | Purpose |
|---|---|---|
| org.openjfx:javafx-controls | 21 | Core JavaFX UI controls (Scene, Stage, Node hierarchy) |
| org.openjfx:javafx-fxml | 21 | FXML support (included but not currently used) |
| org.openjfx:javafx-media | 21 | Media playback support (MediaPlayer, Media) for sound effects |
| com.fasterxml.jackson.core:jackson-databind | 2.15.2 | JSON parsing and data binding - converts JSON to Java objects |
JavaFX 21
Modern UI toolkit for Java desktop applications. Provides rich graphics, media, and UI controls. Requires explicit module declaration in Java 9+.
Key Components Used:
-
javafx.application.Application- Application lifecycle javafx.scene.Scene- Scene graph container-
javafx.scene.layout.Pane- Layout container javafx.scene.control.Label- Text display-
javafx.scene.control.Button- Interactive buttons -
javafx.scene.image.ImageView- Image rendering -
javafx.scene.effect.ColorAdjust- Visual effects -
javafx.scene.media.MediaPlayer- Audio playback javafx.scene.media.Media- Audio resources
JavaFX Media 21
Audio and video playback support for JavaFX. Used in this project for playing the "Who's That Pokemon?" sound effect.
Why JavaFX Media?
- Native support for MP3, WAV, and other audio formats
- Simple API for audio playback control
- Integrated with JavaFX event system
- Good performance for UI sound effects
Key Classes Used:
javafx.scene.media.MediaPlayer and
javafx.scene.media.Media
Jackson Databind 2.15.2
High-performance JSON processor for Java. Handles serialization and deserialization between JSON and POJOs.
Why Jackson?
- Fast and efficient JSON parsing
- Automatic mapping to Java objects
- Support for generic collections (List, Map)
- Annotation-based customization available
- Widely used industry standard
Key Class Used:
com.fasterxml.jackson.databind.ObjectMapper
The project uses JPMS (Java Platform Module System). The
module-info.java file declares module dependencies
and accessibility:
module com.example {
// JavaFX dependencies
requires javafx.controls;
requires javafx.graphics;
requires javafx.media; // Required for sound effects!
requires javafx.fxml;
// Jackson dependencies for JSON parsing
requires com.fasterxml.jackson.databind;
requires com.fasterxml.jackson.core;
requires com.fasterxml.jackson.annotation;
// Export packages (make accessible to other modules)
exports com.example;
exports com.example.controller;
exports com.example.view;
exports com.example.model;
// Open packages for reflection (needed for Jackson)
opens com.example.model to com.fasterxml.jackson.databind;
}
opens directive is
critical for Jackson. Without it, Jackson cannot use reflection to
access private fields in the Pokemon class, resulting in
deserialization errors. Also note that
requires javafx.media is essential for sound
playback.
Key Maven plugin configuration for running the JavaFX application:
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<executions>
<execution>
<id>default-cli</id>
<configuration>
<mainClass>com.example.App</mainClass>
</configuration>
</execution>
</executions>
</plugin>
This plugin handles JavaFX module path configuration
automatically, allowing you to run the application with
mvn javafx:run.
If not using Maven: You'll need to manually configure module paths and classpath. Here's how for different build systems:
Gradle Configuration:
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.14'
}
javafx {
version = "21"
modules = ['javafx.controls', 'javafx.fxml', 'javafx.media']
}
dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'
}
application {
mainModule = 'com.example'
mainClass = 'com.example.App'
}
Manual Compilation (command line):
javac --module-path /path/to/javafx-sdk-21/lib:jackson-jars \
--add-modules javafx.controls,javafx.media,com.fasterxml.jackson.databind \
-d out src/main/java/com/example/*.java
# Run
java --module-path /path/to/javafx-sdk-21/lib:jackson-jars:out \
--add-modules javafx.controls,javafx.media,com.fasterxml.jackson.databind \
com.example.App
Best Practices
Code Organization
- MVC Separation: Keep business logic in Model, UI in View, coordination in Controller. Never access View directly from Model or vice versa - always go through Controller.
- Constants: UI coordinates and dimensions are defined as constants at the top of the View class for easy maintenance. Group related constants together.
- Resource Management: Pass the Class object for resource access to ensure portability between development and production environments. Always use getResourceAsStream() for files in the JAR.
- Error Handling: Check for null resources and handle loading failures gracefully with try-catch blocks and user feedback. Especially important for audio files.
- State Validation: Always verify powered-on state before allowing interactions. Use guard clauses at the start of Controller methods.
- Media Resource Cleanup: Stop previous MediaPlayer instances before starting new ones to prevent audio overlap.
| ✅ DO | ❌ DON'T |
|---|---|
| Keep Model completely UI-independent - should work without JavaFX | Import JavaFX classes in Model layer |
| Make View a passive component that only displays data | Put business logic or state management in View |
| Have Controller mediate all communication between layers | Let Model directly update View or View directly change Model |
| Pass resource Class from App to View and Controller for consistent resource loading | Use getClass() inside View/Controller (may fail in JAR) |
| Keep methods focused with single responsibility (one method does one thing) | Create large multi-purpose methods that do many things |
- JSON Validation: Validate JSON structure before deserialization to prevent parsing errors
- Array Bounds: Check array bounds before accessing Pokemon list (especially with user-driven navigation)
- Resource Existence: Handle missing image files gracefully with fallback images or error messages. Same for audio files.
- Input Sanitization: If adding search functionality, sanitize user input to prevent injection issues
- Data Validation: Validate evolution stage values (1-3), dex numbers (1-151), and ensure required fields exist
- Type Safety: Use strong typing and avoid raw types or excessive casting
Current Implementation:
- Lazy Image Loading: Images are loaded on-demand per Pokemon, not all at startup - saves memory
- JSON Caching: JSON is loaded once at startup and cached in memory for the application lifetime
- Effect Reuse: ColorAdjust effect is created once and reused for all silhouettes
- Font Sharing: Custom font is loaded once and shared across all labels
- Sound Management: MediaPlayer instance is reused, stopping previous playback before starting new
Optimization Opportunities:
- Image Caching: Cache loaded images to avoid reloading when navigating back to previously viewed Pokemon
- Preloading: Preload adjacent Pokemon images in background for smoother navigation
- Virtual Scrolling: If implementing a grid view, only render visible items
- Thumbnail Generation: Create smaller thumbnails for list views to reduce memory usage
- Audio Preloading: Preload sound effect at startup to eliminate first-play delay
- Naming Conventions: Use descriptive names (handlePowerOn not doThing), follow Java naming standards
- Documentation: Add Javadoc comments to public methods explaining parameters and return values
- Method Length: Keep methods short (ideally under 30 lines) - extract complex logic into helper methods
- Magic Numbers: Avoid magic numbers in code - use named constants with descriptive names
- DRY Principle: Don't Repeat Yourself - extract repeated code into reusable methods
- Error Messages: Provide helpful error messages that guide users or developers to solutions
- Logging: Use proper logging (java.util.logging) instead of System.out.println for production code
While the current project doesn't include tests, here's what you should test if expanding:
Model Layer (Easy to Unit Test):
- Navigation boundary conditions (wrap-around)
- Power state transitions
- Reveal/hide toggle behavior
- Data loading and parsing
- State consistency after operations
Controller Layer (Integration Tests):
- Event handler responses
- View update triggers
- State validation enforcement
View Layer (Manual/Visual Tests):
- UI rendering correctness
- Button positioning accuracy
- Effect application (silhouettes)
- Text wrapping and fitting
- Sound playback functionality
.gitignore recommendations:
# Build artifacts
target/
*.class
*.jar
*.war
# IDE files
.idea/
.vscode/
*.iml
.project
.classpath
.settings/
# OS files
.DS_Store
Thumbs.db
# Maven
dependency-reduced-pom.xml
# Logs
*.log
Commit Message Guidelines:
- Use present tense: "Add feature" not "Added feature"
- Be specific: "Fix navigation wrap-around bug" not "Fix bug"
- Reference issues/tickets when applicable
- Keep first line under 50 characters
- Add detailed explanation in body for complex changes