Skip to content
/ httply Public

Retra API annotation-based and Voltra API declarative Queue-based For Android Studio [ Java ]

Notifications You must be signed in to change notification settings

tuhinx/httply

Repository files navigation

Httply

A zero-dependency HTTP networking library for Android

Platform Min SDK License Version JitPack

Httply is a modern, lightweight HTTP networking library for Android that supports both Retra-style (annotation-based) and Voltra-style (request queue-based) APIs. Built with zero external dependencies, it's designed to be simple, efficient, and easy to use.

✨ Features

🔌 Zero External Dependencies - Built entirely with Java standard libraries
🧩 Dual API Styles
Retra API - Declarative, annotation-based API for defining HTTP endpoints
Voltra API - Imperative, queue-based API for making requests
Full Java and Kotlin Support - Works seamlessly with both languages
🪶 Lightweight - Minimal footprint in your app
🔄 Connection Pooling - Reuses connections for better performance
🧠 Configurable Caching - Control whether requests should be cached
⏱️ Configurable Timeouts - Set connect and read timeouts
🧵 Customizable Thread Pools - Control the number of threads used for requests
📋 JSON Support - Built-in JSON parsing with standard org.json library

🛡️ Zero External Dependencies

Zero Dependencies

Httply is designed with a strong focus on minimizing dependencies in your Android projects:

🔒 Security & Reliability

  • No third-party libraries - Reduces risk of dependency conflicts and security vulnerabilities
  • Better long-term stability - No risk of external dependencies becoming deprecated or unmaintained
  • Consistent behavior - No unexpected changes from third-party library updates

⚡ Performance Benefits

  • Smaller APK size - No additional libraries means your app's size stays smaller
  • Faster build times - Fewer dependencies lead to quicker compilation and build processes
  • Reduced method count - Helps stay under the 65K method limit without requiring multidex

🧰 Implementation

  • Built on Java standard libraries - Uses only core Java and Android libraries
  • Clean architecture - Easy to integrate without adding bloat or complexity

📦 Installation

JitPack Integration
Gradle

Step 1: Add JitPack repository to your project-level build.gradle:

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        mavenCentral()
        maven { url 'https://jitpack.io' }
    }
}

Step 2: Add the dependency to your app-level build.gradle:

dependencies {
    implementation 'com.github.tuhinx:httply:1.0.5'
}
Kotlin DSL

Step 1: Add JitPack repository to your settings.gradle.kts:

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}

Step 2: Add the dependency to your app-level build.gradle.kts:

dependencies {
    implementation("com.github.tuhinx:httply:1.0.5")
}

🚀 Usage

Httply provides two powerful API styles to suit your needs

🔄 Retra API

Annotation-based, declarative style

Declarative

🔄 Voltra API

Queue-based, imperative style

Imperative

📝 Retra API

The Retra API provides a clean, annotation-based approach for defining HTTP endpoints.

Step 1: Define an interface with annotated methods

// Define model classes for your API responses
public class FoodItem {
    private String name;
    private String category;
    private String price;

    // Getters
    public String getName() { return name; }
    public String getCategory() { return category; }
    public String getPrice() { return price; }
}

// Define your API interface
public interface FoodApi {
    @GET("v1/861a8605-a6e0-408d-8feb-ab303b15f59f")
    Call<List<FoodItem>> getFoodItems();
}

Step 2: Create a service factory and use the interface

// Create a Retra instance using the builder pattern
Retra retra = new Retra.Builder()
        .baseUrl("https://mocki.io/")
        .addConverterFactory(GsonConverterFactory.create())
        .build();

// Create an implementation of the API interface
FoodApi api = retra.create(FoodApi.class);

// Asynchronous requests with callbacks
Call<List<FoodItem>> call = api.getFoodItems();
call.enqueue(new Callback<List<FoodItem>>() {
    @Override
    public void onResponse(Call<List<FoodItem>> call, RetraResponse<List<FoodItem>> response) {
        if (response.isSuccessful() && response.body() != null) {
            // Process the response on the UI thread
            for (FoodItem item : response.body()) {
                HashMap<String, String> map = new HashMap<>();
                map.put("name", item.getName());
                map.put("category", item.getCategory());
                map.put("price", "$" + item.getPrice());
                foodList.add(map);
            }

            // Update the UI
            FoodAdapter adapter = new FoodAdapter(context, foodList);
            listView.setAdapter(adapter);
            adapter.notifyDataSetChanged();
        } else {
            Toast.makeText(context, "Retra: Empty response", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onFailure(Call<List<FoodItem>> call, Throwable t) {
        Toast.makeText(context, "Retra Error: " + t.getMessage(), Toast.LENGTH_LONG).show();
        // Fallback to Voltra API if Retra fails
        callVoltraApi();
    }
});

🔄 Voltra API

The Voltra API provides a request queue-based approach, perfect for making multiple HTTP requests.

Step 1: Create a RequestQueue

// Initialize a RequestQueue with the default configuration
RequestQueue queue = Httply.newRequestQueue(context);

Step 2: Make requests using the queue

📊 JsonObjectRequest Example
// GET request for a JSON object (lambda style)
String url = "https://mocki.io/v1/861a8605-a6e0-408d-8feb-ab303b15f59f";
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
        Request.Method.GET,
        url,
        null, // No request body for GET
        response -> {
            try {
                // Parse the JSON response
                String name = response.getString("name");
                String category = response.getString("category");

                // Use the data
                System.out.println("Food: " + name + ", Category: " + category);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        },
        error -> {
            // Handle any errors
            error.printStackTrace();
        }
);

// Disable caching for this request (optional)
jsonObjectRequest.setShouldCache(false);

// Add the request to the queue to execute it
queue.add(jsonObjectRequest);
// GET request for a JSON object (anonymous inner class style)
String url = "https://api.example.com/food";
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(
        Request.Method.GET,
        url,
        null,
        new VoltraResponse.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                try {
                    int id = response.getInt("id");
                    String name = response.getString("name");
                    String category = response.getString("category");
                    double price = response.getDouble("price");

                    Toast.makeText(context,
                            name + " (" + category + ") - $" + price,
                            Toast.LENGTH_SHORT).show();
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        },
        new VoltraResponse.ErrorListener() {
            @Override
            public void onErrorResponse(VoltraError error) {
                error.printStackTrace();
                Toast.makeText(context, "Error loading object", Toast.LENGTH_SHORT).show();
            }
        }
);

jsonObjectRequest.setShouldCache(false);

requestQueue.add(jsonObjectRequest);
📋 JsonArrayRequest Example
// GET request for a JSON array (lambda style)
String urlArray = "https://mocki.io/v1/861a8605-a6e0-408d-8feb-ab303b15f59f";
JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(
        Request.Method.GET,
        urlArray,
        null,
        response -> {
            try {
                // Iterate through the JSON array
                for (int i = 0; i < response.length(); i++) {
                    JSONObject food = response.getJSONObject(i);

                    // Extract data from each object
                    String name = food.getString("name");
                    String category = food.getString("category");

                    // Use the data
                    System.out.println("Food " + i + ": " + name + ", " + category);
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        },
        error -> {
            // Handle any errors
            error.printStackTrace();
        }
);

jsonArrayRequest.setShouldCache(false);
// Add the request to the queue to execute it
queue.add(jsonArrayRequest);
// GET request for a JSON array (anonymous inner class style)
String url = "https://api.example.com/foods";
JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(
        Request.Method.GET,
        url,
        null,
        new VoltraResponse.Listener<JSONArray>() {
            @Override
            public void onResponse(JSONArray response) {
                try {
                    for (int i = 0; i < response.length(); i++) {
                        JSONObject item = response.getJSONObject(i);
                        String name = item.getString("name");
                        double price = item.getDouble("price");

                        int finalI = i;
                        Toast.makeText(context,
                                (finalI + 1) + ". " + name + " - $" + price,
                                Toast.LENGTH_SHORT).show();
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        },
        new VoltraResponse.ErrorListener() {
            @Override
            public void onErrorResponse(VoltraError error) {
                error.printStackTrace();
                Toast.makeText(context, "Error loading array", Toast.LENGTH_SHORT).show();
            }
        }
);
jsonArrayRequest.setShouldCache(false);

requestQueue.add(jsonArrayRequest);
📝 StringRequest Example
// GET request for a string response (lambda style)
String url = "https://mocki.io/v1/861a8605-a6e0-408d-8feb-ab303b15f59f";
StringRequest stringRequest = new StringRequest(
        Request.Method.GET,
        url,
        response -> {
            Log.d("TAG", "Raw response: " + response);
            if (response != null && !response.trim().isEmpty()) {
                try {
                    JSONArray jsonArray = new JSONArray(response);
                    for (int i = 0; i < jsonArray.length(); i++) {
                        JSONObject item = jsonArray.getJSONObject(i);
                        HashMap<String, String> map = new HashMap<>();
                        map.put("name", item.getString("name"));
                        map.put("category", item.getString("category"));
                        map.put("price", "$" + item.getString("price"));
                        foodList.add(map);
                    }
                    Toast.makeText(context, "Loaded " + foodList.size() + " items.", Toast.LENGTH_SHORT).show();
                } catch (JSONException e) {
                    Log.e("TAG", "JSON parsing error: " + e.getMessage());
                    Toast.makeText(context, "Invalid JSON format", Toast.LENGTH_SHORT).show();
                }
            } else {
                Toast.makeText(context, "Empty response", Toast.LENGTH_SHORT).show();
            }
        },
        error -> {
            Log.e("TAG", "Voltra Error: " + error.getMessage(), error);
            Toast.makeText(context, "Voltra Error: " + error.getMessage(), Toast.LENGTH_SHORT).show();
        }
);

// Disable caching for this request
stringRequest.setShouldCache(false);

// Add the request to the queue
queue.add(stringRequest);
// GET request for a string response (anonymous inner class style)
String url = "https://mocki.io/v1/861a8605-a6e0-408d-8feb-ab303b15f59f";

StringRequest stringRequest = new StringRequest(
        Request.Method.GET,
        url,
        new VoltraResponse.Listener<String>() {
            @Override
            public void onResponse(String response) {
                Log.d("TAG", "Raw response: " + response);
                if (response != null && !response.trim().isEmpty()) {
                    try {
                        JSONArray jsonArray = new JSONArray(response);
                        for (int i = 0; i < jsonArray.length(); i++) {
                            JSONObject item = jsonArray.getJSONObject(i);
                            HashMap<String, String> map = new HashMap<>();
                            map.put("name", item.getString("name"));
                            map.put("category", item.getString("category"));
                            map.put("price", "$" + item.getString("price"));
                            // Add to your data list
                            foodList.add(map);
                        }
                        Toast.makeText(context, "Loaded " + foodList.size() + " items.", Toast.LENGTH_SHORT).show();
                    } catch (JSONException e) {
                        Log.e("TAG", "JSON parsing error: " + e.getMessage());
                        Toast.makeText(context, "Invalid JSON format", Toast.LENGTH_SHORT).show();
                    }
                } else {
                    Toast.makeText(context, "Empty response", Toast.LENGTH_SHORT).show();
                }
            }
        },
        new VoltraResponse.ErrorListener() {
            @Override
            public void onErrorResponse(VoltraError error) {
                Log.e("TAG", "Voltra Error: " + error.getMessage(), error);
                Toast.makeText(context, "Voltra Error: " + error.getMessage(), Toast.LENGTH_SHORT).show();
            }
        }
);

// Disable caching for this request
stringRequest.setShouldCache(false);

// Add the request to the queue
requestQueue.add(stringRequest);

⚙️ Advanced Configuration

Advanced Configuration

🚀 Performance Optimization

Httply is designed with performance in mind, offering several features to optimize your network operations:

Efficient Resource Usage - Minimizes memory and CPU consumption
🔄 Connection Reuse - Reduces connection establishment overhead
📦 Minimal Footprint - Zero dependencies means smaller APK size
⏱️ Configurable Timeouts - Fine-tune network behavior for your use case

🧠 Request Caching

Httply allows you to control whether individual requests should be cached:

// Create a request
StringRequest stringRequest = new StringRequest(
        Request.Method.GET,
        "https://mocki.io/v1/861a8605-a6e0-408d-8feb-ab303b15f59f",
        new VoltraResponse.Listener<String>() {
            @Override
            public void onResponse(String response) {
                // Process the response
                Log.d("TAG", "Raw response: " + response);
                // Parse JSON and update UI
            }
        },
        new VoltraResponse.ErrorListener() {
            @Override
            public void onErrorResponse(VoltraError error) {
                // Handle errors
                Log.e("TAG", "Voltra Error: " + error.getMessage(), error);
            }
        }
);

// Disable caching for this request
stringRequest.setShouldCache(false);

// Add the request to the queue
requestQueue.add(stringRequest);

By default, all requests are cached. Setting setShouldCache(false) will:

  • Prevent the connection from being reused for future requests
  • Force a new connection to be established for this request
  • Useful for requests that need fresh data or when troubleshooting network issues

🔧 Custom HTTP Client

Customizable HTTP Client

Httply provides a powerful, customizable HTTP client that forms the foundation of both the Retra and Voltra APIs. The client is built on Java's standard HttpURLConnection but adds several advanced features:

⏱️ Configurable Timeouts - Set custom connect and read timeouts to handle slow networks
🔄 Connection Pooling - Reuse connections to improve performance and reduce latency
🧵 Custom Thread Pools - Configure the executor used for asynchronous requests
🔀 Redirect Control - Enable or disable following HTTP redirects
🧠 Caching Control - Fine-grained control over which requests should be cached

Basic Configuration

// Create a custom HTTP client with advanced configuration
HttpClient client = Httply.newHttpClient()
        .connectTimeout(15, TimeUnit.SECONDS)  // Set connection timeout
        .readTimeout(30, TimeUnit.SECONDS)     // Set read timeout
        .followRedirects(true)                 // Enable following redirects
        .build();

Advanced Configuration

// Create a custom connection pool
ConnectionPool connectionPool = new ConnectionPool(
        10,                     // Maximum idle connections
        5, TimeUnit.MINUTES     // Keep-alive duration
);

// Create a custom executor for async requests
Executor executor = Executors.newFixedThreadPool(4);

// Create a fully customized HTTP client
HttpClient client = Httply.newHttpClient()
        .connectionPool(connectionPool)
        .executor(executor)
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(60, TimeUnit.SECONDS)
        .followRedirects(false)  // Disable following redirects
        .build();
Use with Retra API
// Configure a Retra instance with the custom client
Retra retra = new Retra.Builder()
        .baseUrl("https://api.example.com/")
        .client(client)
        .addConverterFactory(JsonConverterFactory.create())
        .build();

// Use Retra to create API interfaces
ApiService api = retra.create(ApiService.class);
Use with Voltra API
// Configure a RequestQueue with the custom client
RequestQueue queue = Httply.newRequestQueue(
        context,
        client
);

// Use the queue to make requests
queue.add(new StringRequest(...));

Connection Pooling

Httply's HTTP client includes a built-in connection pooling mechanism that significantly improves performance by reusing existing connections:

Reduced Latency - Eliminates the overhead of establishing new connections
🔋 Lower Resource Usage - Reduces CPU, memory, and battery consumption
📈 Improved Throughput - Handles more requests with fewer resources
⚙️ Configurable - Customize pool size and connection keep-alive duration
// Create a custom connection pool with 20 max idle connections
// and a 10-minute keep-alive duration
ConnectionPool connectionPool = new ConnectionPool(
        20,                      // Maximum idle connections
        10, TimeUnit.MINUTES     // Keep-alive duration
);

// Use the custom connection pool with your HTTP client
HttpClient client = Httply.newHttpClient()
        .connectionPool(connectionPool)
        .build();

Thread Pool Customization

Httply allows you to customize the thread pool used for asynchronous requests, giving you control over resource usage and concurrency:

🧵 Fixed Thread Pool - Limit the number of concurrent requests
⏱️ Scheduled Executor - Schedule requests to run at specific times
🔄 Single Thread - Ensure requests are processed sequentially
🚀 Cached Thread Pool - Dynamically adjust thread count based on load
// Create a fixed thread pool with 4 threads
Executor fixedPool = Executors.newFixedThreadPool(4);

// Create a single-threaded executor for sequential processing
Executor singleThread = Executors.newSingleThreadExecutor();

// Create a cached thread pool that adjusts based on load
Executor cachedPool = Executors.newCachedThreadPool();

// Use a custom executor with your HTTP client
HttpClient client = Httply.newHttpClient()
        .executor(fixedPool)  // Choose the appropriate executor for your needs
        .build();

Direct Usage

For advanced use cases, you can also use the HTTP client directly:

// Create an HTTP client
HttpClient client = Httply.newHttpClient().build();

// Create a request
HttpRequest request = new HttpRequest.Builder()
        .url("https://mocki.io/v1/861a8605-a6e0-408d-8feb-ab303b15f59f")
        .method(HttpMethod.GET)
        .shouldCache(true)  // Enable caching (default)
        .build();

// Execute synchronously
try {
    HttpResponse response = client.execute(request);
    if (response.isSuccessful()) {
        String body = response.body().string();
        // Process the response
    }
} catch (IOException e) {
    e.printStackTrace();
}

// Or execute asynchronously
client.executeAsync(request, new HttpClient.Callback() {
    @Override
    public void onResponse(HttpResponse response) {
        // Process the response
    }

    @Override
    public void onFailure(Exception e) {
        // Handle the error
    }
});

🔍 Troubleshooting

Troubleshooting Guide

Common Issues and Solutions

🔌 Connection Timeout
If you're experiencing connection timeouts, try increasing the timeout values:
HttpClient client = Httply.newHttpClient()
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build();
🔄 Stale Connections
If you suspect stale connections in the pool, disable caching for critical requests:
request.setShouldCache(false);
🧵 Thread Pool Exhaustion
If you're making many concurrent requests, consider using a larger thread pool:
Executor executor = Executors.newFixedThreadPool(8);
HttpClient client = Httply.newHttpClient()
        .executor(executor)
        .build();
🔒 SSL/TLS Issues
For HTTPS connections, ensure your server's certificates are valid and trusted.

📄 License

MIT License
View MIT License Text
MIT License

Copyright (c) 2025 TuhinX

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Made with ❤️ by Tuhinx

About

Retra API annotation-based and Voltra API declarative Queue-based For Android Studio [ Java ]

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages