Building a Reliable Android App: Embracing Legacy Compatibility and Minimal Dependencies

Introduction

In the fast-paced world of technology, staying up to date with the latest trends and adopting new tech stacks is often seen as crucial for success. However, there are instances when clients prefer a more cautious approach. They may prioritize compatibility with legacy systems and worry about the risk of investing in a stack that could potentially be abandoned. In this blog post, I will share my experience of working on such a project, where I was tasked with building an Android app in Java, leveraging XML requests and minimizing the use of external libraries.

Challenge

When I received the requirement to build an Android app in Java, it presented a unique challenge. At the time, Kotlin had gained popularity in the Android community, but the client preferred sticking with Java due to concerns over compatibility and the fear of adopting an emerging language.

Furthermore, the project had additional constraints: security considerations and the need to maintain a lean codebase free from unnecessary dependencies. While using external libraries can often provide shortcuts and pre-built solutions, it was strongly recommended to minimize their usage, ensuring the app remained secure, efficient, and easy to maintain.

Solution

To tackle these challenges and deliver the best possible outcome, I devised a solution that addressed the client's requirements while maintaining compatibility and efficiency.

  1. Generic Extensible HttpRequest Class: I began by creating a generic and extensible HttpRequest class. This class would serve as the foundation for making requests to the server and handling different types of data responses. By making it extensible, I ensured that the client could accommodate various data formats in the future, such as strings, bitmaps, JSON objects, JSON arrays, or even file downloads.

Parts of the HttpRequest class as shown.

public abstract class HttpRequest<T> {
    public enum Method {
        GET,
        POST,
        PUT,
        DELETE
    }
    public enum ContentType {
        JSON,
        XML
    }

    private final String rootUrl;
    private Method method = Method.GET;
    private T response;

    public HttpRequest(String url) {
        this.rootUrl = url;
    }

    public HttpRequest(String url, Method method) {
        this(url);
        this.method = method;
    }

    public T makeRequest() throws Exception {
        //make your actual request here
        ...
        //parse response below
        parseResponse(rawResponse);
        return response;
    }

    protected void setResponse(T response) {
        this.response = response;
    }

    protected abstract void parseResponse(String rawResponse) throws Exception;

}

An example of a BitmapRequest class extending the HttpRequest class

public class BitmapRequest extends HttpRequest<Bitmap> {
    public BitmapRequest(String url) {
        super(url);
    }

    @Override
    protected void parseResponse(String rawResponse) throws Exception {
        if (rawResponse != null && !rawResponse.isEmpty()) {
            byte[] imageBytes = Base64.decode(rawResponse, Base64.DEFAULT);
            if (imageBytes != null && imageBytes.length > 0) {
                Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
                setResponse(bitmap);
            }
        }
    }
}

How you would call this BitmapRequest class


BitmapRequest bitmapRequest = new BitmapRequest(url);
bitmapRequest.setContentType(HttpRequest.ContentType.XML);
Bitmap = bitmapRequest.makeRequest();

  1. XML Requests and Response Parsing: Given the client's preference for XML requests, I implemented the necessary functionality to make XML requests to the server. The HttpRequest class would handle the communication with the server and retrieve the XML response.

To appropriately parse the XML response, I utilized Java's built-in XML parsing capabilities, such as the XMLPullParser. This allowed me to extract the required data from the response efficiently. By encapsulating the parsing logic within the data class extending the HttpRequest, I ensured that the app received the parsed data in the appropriate format.

private String readXmlResponse(InputStream inputStream) throws XmlPullParserException, IOException {
    XmlPullParser parser = Xml.newPullParser();
    parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
    InputStream readStream = new BufferedInputStream(inputStream);
    parser.setInput(readStream, null);
    parser.nextTag();
    parser.next();
    String rawResponse = parser.getText();
    readStream.close();
    closeConnection();
    return rawResponse;
}
  1. Minimal External Dependencies: To fulfill the client's request of minimizing external dependencies, I avoided incorporating additional libraries wherever possible. Instead, I relied on the native features and capabilities provided by the Android SDK and Java language itself. This approach not only reduced the risk of relying on third-party libraries but also helped keep the codebase lean and maintainable.

Benefits and Advantages

Embracing the client's preference for a more established technology stack had several advantages:

  1. Compatibility with Legacy Systems: By using Java instead of Kotlin, I ensured seamless integration with the client's existing legacy systems. This eliminated any potential compatibility issues and facilitated a smoother transition for their users.

  2. Reduced Risk of Abandoned Technologies: While adopting the latest tech stack may offer cutting-edge features, there is always a risk of the creators abandoning or significantly altering the technology in the future. By building on a proven technology like Java, the client mitigated this risk and gained more stability and longevity for their app.

  3. Security and Bloatware-Free Codebase: Minimizing the use of external libraries reduced the attack surface and potential vulnerabilities in the app. Additionally, it helped maintain a clean and efficient codebase, free from unnecessary bloatware and dependencies. This not only improved performance but also simplified the app's maintenance and troubleshooting process.

Conclusion

Working on a project where clients prioritize legacy compatibility and minimal external dependencies can be challenging but rewarding. By embracing the client's preferences and leveraging the power of Java, I successfully built an Android app capable of making XML requests, parsing responses, and delivering a secure and efficient user experience. This project showcased the importance of considering compatibility, security, and codebase efficiency when developing software solutions. By carefully evaluating the requirements and tailoring the approach, we can deliver reliable and future-proof solutions that meet the unique needs of our clients.

If you find yourself facing similar challenges, remember to assess the benefits of adopting proven technologies, design extensible and adaptable solutions, and prioritize security and minimal dependencies. Doing so will not only address immediate project requirements but also set a strong foundation for long-term success.

I hope this article has provided valuable insights and inspiration for your own projects.

View Full Codebase

Happy coding!