Sonntag, 13. Oktober 2013

BARACUS from Scratch : Part 2 - Defining the application context and making use of Dependency Injection (DI+IOC)

Table of Contents

Previous Tutorial : Setting up the Application

In this tutorial I am going to show You how to launch the application under control of the Baracus Framework, how to define basic beans, how to inject them and how to take control of the bean's lifecycle.

Download the sourcecode of this tutorial from Github

NOTICE

This tutorial is intended to understand how the wiring is done. If you just want to use the framework, simply run a mvn archetype:generate and search for BARACUS. An archetype containing an working example configuration has been released recently to ease the use of the framework.



Step 1 : Make BARACUS to Your application context

In order to make the BARACUS Application Context to the android application context, You have to inherit the BaracusApplicationContext class.

In case of Android version 3.2,  you must leave the SDK-Level in the AndroidManifest.xml at 14. You can use this libs in a Version 13 level environment, if You do not call the Level 14 functions. A V32 Compatibility lib is planned but not available yet. You might run into problems!

In this class, You have to register all bean classes manually. I chose this strategy in order to avoid code generation or excessive use of reflection inside of the application context. So basically, Your ApplicationContext looks like this after the first step :


package org.baracus.application;
import org.baracus.context.BaracusApplicationContext;

/**
 * Created with IntelliJ IDEA.
 * User: marcus
 */
public class ApplicationContext extends BaracusApplicationContext {

}

So far, so good, but this class needs to be registered as "the" application management class in the AndroidManifest.xml. Android supports the android:name attribute for this. So You simply add it to the application-tag, carrying the classname without the packagename

android:name=".application.ApplicationContext"

which leads us to this AndroidManifest.xml :


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.baracus"
    android:versionCode="1"
    android:versionName="1.0-SNAPSHOT" >

    <uses-sdk
        android:minSdkVersion="13"
        android:targetSdkVersion="16" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:name=".application.ApplicationContext"
        android:theme="@style/AppTheme">
        <activity android:name=".HelloAndroidActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

That's it. Now the Baracus Framework will be the registered Application Context for the android app. If You are using Android 3.2 You need to the lifecycle management manually.

Step 2 : The OpenHelper

Because Baracus also supports the lifecycle of Your database, You must implement an OpenHelper, even if You do not have any database support (You implicitly will have a database because of the ConfigurationDao inside of the framework). You have to define two vital information elements there :

The name of the application database and the target version.

This can be easily done like this :

package org.baracus.application;

import android.content.Context;
import org.baracus.dao.BaracusOpenHelper;

/**
 * Created with IntelliJ IDEA.
 * User: marcus
 */
public class OpenHelper extends BaracusOpenHelper {

    private static final String DATABASE_NAME="tutorial-app.db";
    private static final int TARGET_VERSION=100;

    /**
     * Open Helper for the android database
     *
     * @param mContext              - the android context
     */
    public OpenHelper(Context mContext) {
        super(mContext, DATABASE_NAME, TARGET_VERSION);
    }
}
This class needs to be registered in Your ApplicationContext, see below for details. In a later tutorial, I will explain how to use the OpenHelper for supporting consistent database handling and migration.

Step 3 : Define and register Your first beans

Now we can start defining bean classes managed by Baracus. This is much more easier than You might think and is done in two simple steps : 1. Define class, 2. Register the class in Your ApplicationContext.

At first, we create a simple Java service class containing a logger and some simple output function :

@Bean
public class CustomerService {

    private static final Logger logger = new Logger(CustomerService.class);

    public void testService() {
        logger.info("Hooray! I have been called!");
    }
}

This is really simple. Now we have to register this bean as a service bean in Our application context, which makes Our ApplicationContext looking like this :
package org.baracus.application;

import org.baracus.context.BaracusApplicationContext;
import org.baracus.service.CustomerService;

/**
 * Created with IntelliJ IDEA.
 * User: marcus
 */
public class ApplicationContext extends BaracusApplicationContext {

    static {
        registerBeanClass(OpenHelper.class);
        registerBeanClass(CustomerService.class);
    }

}
Now, we are able to auto-inject this bean into any managed component. Because Baracus is the ApplicationContext of Our application, we easily are able to inject the bean by referencing the type in the HelloWorldActivity:

package org.baracus;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import org.baracus.annotations.Bean;
import org.baracus.service.CustomerService;

public class HelloAndroidActivity extends Activity {

    @Bean
    CustomerService customerService;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
 getMenuInflater().inflate(org.baracus.R.menu.main, menu);
 return true;
    }
Please notice, the @Bean annotation is only for documentation purpose and has no runtime retention! The injection mechanism is based on the type of a class! This is a very efficient way of dealing with classes, but it disallows messing around with complex type hierarchies. The bean class always must be an instantiatable POJO!

There always already exists some beans inside of Your container, the ConfigurationDao, which allows You to store configuration tokens into the applications database (key-value), the ValidationFactory used for form validation, and the ErrorHandlingFactory for routing errors to the form components, but these beans are going to be explained mored detailed in a later tutorial.

Step 4 : Bean lifecycle

Baracus allows You to define lifecylce functions; You may define a initialization and a destruction function. This functions are close to the JEE @PostConstruct and @PreDestroy functions, but are basing on interfaces. They bring the concept of IOC (Inversion of Control) into the Baracus Framework and are used to make init code easier and manageable by the container. Notice, You normally never call these function by Yourself! They are called by Baracus BeanContainer (IOC, You know?) In order to influence the lifecycle of, let Your bean simply implement Initializeable (for Post-Construct-alike behaviour) or Destroyable (for Pre-Destroy-alike behaviour) :

package org.baracus.service;

import org.baracus.annotations.Bean;
import org.baracus.dao.ConfigurationDao;
import org.baracus.lifecycle.Destroyable;
import org.baracus.lifecycle.Initializeable;
import org.baracus.util.Logger;

/**
 * Created with IntelliJ IDEA.
 * User: marcus
 * Date: 13.10.13
 * Time: 15:06
 * To change this template use File | Settings | File Templates.
 */
@Bean
public class CustomerService implements Initializeable, Destroyable {

    private static final Logger logger = new Logger(CustomerService.class);

    @Bean
    ConfigurationDao configurationDao;

    public void testService() {
        logger.info("Hooray! I have been called!");
    }

    @Override
    public void onDestroy() {
        logger.info("Bean destruction initiated!");
    }

    @Override
    public void postConstruct() {
        logger.info("Bean is created. Is the ConfigurationDao correctly injected : $1", (configurationDao != null));
    }
}


Finish : The first real usage

Finally, we add some button to the form in order to have some application feedback. At first, we create the button in the activity_main.xml :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" android:id="@+id/textView"/>

    <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Test"
            android:id="@+id/button" android:layout_alignRight="@+id/textView" android:layout_alignParentTop="true"
            android:layout_marginTop="39dp"
            android:onClick="onButtonTestClicked"/>

</RelativeLayout>

Then, we create the button callback :


package org.baracus;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import org.baracus.annotations.Bean;
import org.baracus.service.CustomerService;

public class HelloAndroidActivity extends Activity {
    static final Logger logger = new Logger(HelloAndroidActivity.class);
    static {
        Logger.setTag("TUTORIAL_APP");
    }
    @Bean
    CustomerService customerService;

    /**
     * Called when the activity is first created.
     * @param savedInstanceState If the activity is being re-initialized after 
     * previously being shut down then this Bundle contains the data it most 
     * recently supplied in onSaveInstanceState(Bundle). <b>Note: Otherwise it is null.</b>
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
 // Inflate the menu; this adds items to the action bar if it is present.
 getMenuInflater().inflate(org.baracus.R.menu.main, menu);
 return true;
    }

    public void onButtonTestClicked(View v) {
        customerService.testService();        
    }

}

In the start activity I set the Logger-Tag, this makes grepping the logfile lotsa easier :) Now all prerequisites are met, we can launch the application and track the logfile
.... 10-13 16:05:49.460: DEBUG/TUTORIAL_APP(10555): CustomerService Bean is created. Is the ConfigurationDao correctly injected : true .... 10-13 16:06:01.290: DEBUG/TUTORIAL_APP(10555): CustomerService Hooray! I have been called! .... Notice, the bean's destruction message would become visible, if You explicitly shutdown the bean container. Simply exiting the app will not make the container stop, because the application will continue running in the background of Your android device.


Next Tutorial : Basic Persistence Mapping

Keine Kommentare:

Kommentar veröffentlichen