3

I'm facing some complexities when developing a medium-complex Android application. I'm searching for information about the possibility of using code-behind-like techniques for easier maintanability of Android software.

Currently (please highlight anything wrong), I have found that in order to make a multi-step wizard with extra dialogs (eg. dialogs that are not part of the main sequence) I need to code a single XML layout file with a single ViewFlipper containing each subview as child node. Today I discovered how to navigate across views more than forward/backward (viewFlipper.setDisplayedChild(i)), giving access to extra views.

Now all the Java code is contained within the main Activity class, which is beginning to look bad. As an experienced .NET developer I have learned how to use custom controls to wrap both layout and business logic inside modules.

I know that in Android I can define a view programmatically as an independent class and add it to the main layout programmatically, however I want to know if it's possible in Android to define a layout by XML (for easier WYSIWYG creation/editing) and define all the code within a dedicated class, with initialization logic, button callbacks, async tasks, etc.

I'm not sure if it's feasible or there is a good compromise that can be achieved.

I have read this question without clearing my doubts.

Thank you.

Code examples:

An extract of the layout file (I expect 4 wizard steps, a help view and an EULA view)

<?xml version="1.0" encoding="utf-8"?>
<ViewFlipper xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/view_phone"
    style="@android:style/Theme.Light.NoTitleBar"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <!-- First screen/welcome -->

    <LinearLayout
        android:id="@+id/view_phone_screen1"
        style="@android:style/Theme.Light.NoTitleBar"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical"
        android:weightSum="100" >

        <TextView
            android:id="@+id/view_phone_screen1_lblChooseProvider"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical|center_horizontal"
            android:text="@string/view_phone_lblChooseProvider_1ststep"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <ImageButton
            android:id="@+id/view_phone_btnFrecciarossa"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:clickable="true"
            android:contentDescription="@string/provider_FRECCIAROSSA"
            android:gravity="center_vertical|clip_vertical"
            android:padding="10dp"
            android:src="@drawable/logo_frecciarossa"
            android:tag="@+id/provider_FRECCIAROSSA" />

        <ImageButton
            android:id="@+id/view_phone_btnItalo"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:clickable="true"
            android:contentDescription="@string/provider_ITALO"
            android:gravity="center_vertical|clip_vertical"
            android:padding="10dp"
            android:src="@drawable/logo_italo"
            android:tag="@+id/provider_ITALO" />
    </LinearLayout>


    <!-- Second screen - will need to do some asynchronous task -->
    <RelativeLayout
        android:id="@+id/view_phone_screen2"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >

        <TextView
            android:id="@+id/view_phone_screen2_lblConnectingToWifi"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical|center_horizontal"
            android:text="@string/view_phone_lblConnectToWifi_2ndstep"
            android:textAppearance="?android:attr/textAppearanceLarge" />


        <TextView
            android:id="@+id/view_phone_step2_lblConnectedToWifi"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/imageView1"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="58dp"
            android:text="@string/view_phone_step2_connectingToWifi"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <TextView
            android:id="@+id/view_phone_step2_lblPhoneNumber"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignLeft="@+id/editText1"
            android:layout_below="@+id/view_phone_step2_lblConnectedToWifi"
            android:layout_marginTop="51dp"
            android:text="@string/view_phone_step2_msgInputPhoneNumber"
            android:textAppearance="?android:attr/textAppearanceMedium" />

        <TextView
            android:id="@+id/view_phone_step2_lblUnableDetectPhoneNumber"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:text="@string/view_phone_step2_msgUnableDetectPhoneNumber"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:visibility="invisible" />

        <Button
            android:id="@+id/view_phone_screen2_backward"
            style="@style/buttonBackward" />

        <Button
            android:id="@+id/view_phone_screen2_forward"
            style="@style/buttonForward_disabled"
            android:enabled="false" />

        <EditText
            android:id="@+id/view_phone_step2_txtPhoneNumber"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignRight="@+id/view_phone_step2_lblPhoneNumber"
            android:layout_below="@+id/view_phone_step2_lblPhoneNumber"
            android:inputType="phone"
            android:singleLine="true" >

            <requestFocus />
        </EditText>
    </RelativeLayout>

</ViewFlipper>

Code example from Activity (expect to implement ALL the logic of 4+2 step wizard)

public class MyActivity extends Activity {
/** Called when the activity is first created. */

private final static String LOG_TAG = "LOG_TAG";
private int stepNumber;

@Override
public void onCreate(Bundle savedInstanceState) {
    this.requestWindowFeature(Window.FEATURE_NO_TITLE);
    this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

    super.onCreate(savedInstanceState);

    this.stepNumber=1;

    setContentView(R.layout.view_phone);

    //This class wraps the click for the two buttons
    ProviderSelectionListener providerSelectionListener = new ProviderSelectionListener(this);
    this.findViewById(R.id.view_phone_btnFrecciarossa).setOnClickListener(providerSelectionListener);
    this.findViewById(R.id.view_phone_btnItalo).setOnClickListener(providerSelectionListener);
}

@Override
protected void onPause() {
    super.onPause();

    try {
        if (MyApplication.getPlatformManager() != null)
            MyApplication.getPlatformManager().onApplicationPause();
    } catch (MyCustomException e) {
        // WTF (Worse Than Failure!)
        Log.e(LOG_TAG, super.getString(R.string.zf_error_unknown_error_pauseactivity), e);
        e.printStackTrace();
    }
}

@Override
protected void onResume() {
    super.onResume();

    try {
        if (MyApplication.getPlatformManager() != null)
            MyApplication.getPlatformManager().onApplicationResume();
    } catch (MyCustomException e) {
        // WTF (Worse Than Failure!)
        Log.e(LOG_TAG, super.getString(R.string.zf_error_unknown_error_pauseactivity), e);
        e.printStackTrace();
    }
}

/*
 * SLIDE INIZIO
 */
protected void slideNext() {
    ViewFlipper vf = (ViewFlipper) findViewById(R.id.view_phone);

    vf.setOutAnimation(getApplicationContext(), R.anim.slide_out_left);
    vf.setInAnimation(getApplicationContext(), R.anim.slide_in_right);
    vf.showNext();

}

protected void slidePrevious() {
    ViewFlipper vf = (ViewFlipper) findViewById(R.id.view_phone);

    vf.setOutAnimation(getApplicationContext(), R.anim.slide_out_right);
    vf.setInAnimation(getApplicationContext(), R.anim.slide_in_left);
    vf.showPrevious();
}

/*
 * SLIDE FINE
 */

/*
 * STEP 1 INIZIO
 */
public void completeStep1(ISmsWifiProvider provider) {
    if (provider == null) {
        Log.e(LOG_TAG, "Provider nullo");
        return;
    }

    MyApplication.setAuthenticationProvider(provider);

    slideNext();

    initializeStep2();
}

public void returnToStep1() {
    MyApplication.setAuthenticationProvider(null);
    slidePrevious();
}

/*
 * STEP 1 FINE
 */

/*
 * STEP 2 INIZIO
 */

private void initializeStep2() {
    // Event handler
    Button backButton = (Button) findViewById(R.id.view_phone_screen2_backward), fwButton = (Button) findViewById(R.id.view_phone_screen2_forward);
    fwButton.setEnabled(false);
    backButton.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            returnToStep1();
        }
    });

}

/*
 * STEP 2 FINE
 */


@Override
public void onBackPressed() {
    // This will be called either automatically for you on 2.0
    // or later, or by the code above on earlier versions of the
    // platform.
    return;
}

}
Community
  • 1
  • 1
usr-local-ΕΨΗΕΛΩΝ
  • 26,101
  • 30
  • 154
  • 305
  • I think it is just a matter of refactoring a little bit your code... is it too 'private'? I mean, we would have more ideas to help you if you share your code. – Cristian Mar 19 '12 at 22:13
  • It's not a privacy concern, but a complexity concern. Anyway I'll share a bit (still work in progress) – usr-local-ΕΨΗΕΛΩΝ Mar 19 '12 at 22:17
  • OK I just discovered the "include" tag for easier modularity of XML layouts inside a multi-view layout. Refactoring **that** code – usr-local-ΕΨΗΕΛΩΝ Mar 19 '12 at 22:29
  • Multiple Screens can/should be implemented in different Activities / Fragments. That also results in less code per class and should work well for a wizard (especially if you just want to go foward and backwards) – zapl Mar 19 '12 at 22:30
  • @zapl I have read that in order to achieve (I forgot to mention) the *slide* animation in a wizard I must use the viewflipper. I'm currently unable to find where I read this. Actually, having multiple activities and an application class that holds the main logic would be close to a panacea (Activity being the "code-behind" of my views) – usr-local-ΕΨΗΕΛΩΝ Mar 19 '12 at 22:33
  • 2
    http://blog.blundell-apps.com/animate-an-activity/ I guess you can customize the animation that happens when switching between activities. – zapl Mar 19 '12 at 22:52

1 Answers1

0

I want to know if it's possible in Android to define a layout by XML (for easier WYSIWYG creation/editing) and define all the code within a dedicated class, with initialization logic, button callbacks, async tasks, etc.

Yes. It is one of the techniques for creating a custom View. For example, I have a custom ColorMixer widget in this project, which can be used directly in an activity, or in a dialog, or in a custom preference.

I agree with your tactical decision to implement a wizard via a ViewFlipper -- see this answer to another StackOverflow question for "Murphy's Theory of the Activity".

I suspect that the right answer, longer term, is for somebody (who might be me) to come up with a Fragment-based wizard pattern, as that gives you the decoupling you desire.

Community
  • 1
  • 1
CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Unfortunately, that's exactly what I don't want. I know how to define custom views programmatically, but I just would like to define their appearence via XML and their behaviour (click, async events...) in Java. One XML + one Java class is what I want. Perhaps if I can use multiple views with animations I can achieve a great result – usr-local-ΕΨΗΕΛΩΝ Mar 20 '12 at 00:15
  • 1
    @djechelon: " I just would like to define their appearence via XML and their behaviour (click, async events...) in Java." -- which is precisely what I am talking about. – CommonsWare Mar 20 '12 at 00:18
  • Perhaps I should have taken a look at ParcelHelper class. +1 :) – usr-local-ΕΨΗΕΛΩΝ Mar 20 '12 at 01:20
  • Could you describe me how did you make that ColorMixer or perhaps link me to a tutorial? I think that the key is your ParcelHelper class, but I believe I need some guidance. – usr-local-ΕΨΗΕΛΩΝ Mar 20 '12 at 11:05
  • 1
    @djechelon: I cover `ColorMixer` in _The Busy Coder's Guide to Advanced Android Development_. One of the Creative Commons licensed older editions has that chapter: http://commonsware.com/AdvAndroid/AdvAndroid-1_9-CC.pdf – CommonsWare Mar 20 '12 at 11:21