Commit a69f12c2 authored by Daniel Vogel's avatar Daniel Vogel

updated MVC examples, added MVVM example

parent 8c670a7e
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 17 compileSdkVersion 26
buildToolsVersion "27.0.1"
defaultConfig { defaultConfig {
applicationId "com.example.mvc" applicationId "cs349.uwaterloo.ca.mvc1"
minSdkVersion 15 minSdkVersion 23
targetSdkVersion 26 targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
productFlavors {
}
} }
dependencies { dependencies {
compile 'com.android.support:support-v4:18.0.0' implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
} }
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
package cs349.uwaterloo.ca.mvc1;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest
{
@Test
public void useAppContext() throws Exception
{
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("cs349.uwaterloo.ca.mvc1", appContext.getPackageName());
}
}
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest package="cs349.uwaterloo.ca.mvc1"
package="com.example.mvc" xmlns:android="http://schemas.android.com/apk/res/android">
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="17" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@drawable/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme" > android:roundIcon="@mipmap/ic_launcher_round"
<activity android:supportsRtl="true"
android:name="com.example.mvc.MainActivity" android:theme="@style/AppTheme">
android:label="@string/app_name" > <activity android:name=".MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>
</manifest> </manifest>
\ No newline at end of file
package com.example.mvc;
import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.ViewGroup;
public class MainActivity extends Activity {
Model model;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("MVC", "onCreate");
// load the base UI (has places for the views)
setContentView(R.layout.mainactivity);
// Setup model
model = new Model();
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
Log.d("MVC", "onPostCreate");
// can only get widgets by id in onPostCreate for activity xml res
// create the views and add them to the main activity
View1 view1 = new View1(this, model);
ViewGroup v1 = (ViewGroup) findViewById(R.id.mainactivity_1);
v1.addView(view1);
View2 view2 = new View2(this, model);
ViewGroup v2 = (ViewGroup) findViewById(R.id.mainactivity_2);
v2.addView(view2);
// initialize views
model.initObservers();
}
// save and restore state (need to do this to support orientation change)
@Override
protected void onSaveInstanceState(Bundle outState) {
Log.d("MVC", "save state");
// save all stateful values in model
outState.putInt("Counter", model.getCounterValue());
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
Log.d("MVC", "restore state");
super.onRestoreInstanceState(savedInstanceState);
// get all stateful values in model
model.setCounterValue(savedInstanceState.getInt("Counter"));
}
}
package com.example.mvc;
import android.util.Log;
import java.util.Observable;
import java.util.Observer;
public class Model extends Observable {
private int counter;
Model() {
counter = 0;
}
// Data methods
public int getCounterValue() {
return counter;
}
public void setCounterValue(int i) {
counter = i;
Log.d("DEMO", "Model: set counter to " + counter);
setChanged();
notifyObservers();
}
public void incrementCounter() {
counter++;
Log.d("DEMO", "Model: increment counter to " + counter);
setChanged();
notifyObservers();
}
// Observer methods
// a helper to make it easier to initialize all observers
public void initObservers() {
setChanged();
notifyObservers();
}
@Override
public void addObserver(Observer observer) {
Log.d("DEMO", "Model: Observer added");
super.addObserver(observer);
}
@Override
public synchronized void deleteObserver(Observer observer) {
Log.d("DEMO", "Model: Observer deleted");
super.deleteObserver(observer);
}
@Override
public synchronized void deleteObservers() {
super.deleteObservers();
}
@Override
public void notifyObservers() {
Log.d("DEMO", "Model: Observers notified");
super.notifyObservers();
}
}
\ No newline at end of file
package com.example.mvc;
import java.util.Observable;
import java.util.Observer;
import android.content.Context;
import android.util.*;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
public class View1 extends LinearLayout implements Observer {
private Model model;
private Button button;
public View1(Context context, Model m) {
super(context);
Log.d("DEMO", "View1 constructor");
// get the xml description of the view and "inflate" it
// into the display (kind of like rendering it)
View.inflate(context, R.layout.view1, this);
// save the model reference
model = m;
// add this view to model's list of observers
model.addObserver(this);
// get a reference to widgets to manipulate on update
button = (Button) findViewById(R.id.view1_button);
// create a controller for the button
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// do this each time the button is clicked
model.incrementCounter();
}
});
}
// the model call this to update the view
public void update(Observable observable, Object data) {
Log.d("DEMO", "update View1");
// update button text with click count
// (convert to string, or else Android uses int as resource id!)
button.setText(String.valueOf(model.getCounterValue()));
}
}
package com.example.mvc;
import java.util.Observable;
import java.util.Observer;
import android.content.Context;
import android.util.*;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
public class View2 extends LinearLayout implements Observer {
private Model model;
private TextView textview;
public View2(Context context, Model m) {
super(context);
Log.d("DEMO", "View2 constructor");
// get the xml description of the view and "inflate" it
// into the display (kind of like rendering it)
View.inflate(context, R.layout.view2, this);
// save the model reference
model = m;
// add this view to model's list of observers
model.addObserver(this);
// get a reference to widgets to manipulate on update
textview = (TextView)findViewById(R.id.view2_textview);
// create a controller to increment counter when clicked
textview.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// do this each time the button is clicked
model.incrementCounter();
}
});
}
// the model call this to update the view
public void update(Observable observable, Object data) {
Log.d("DEMO", "update View2");
int n = model.getCounterValue();
StringBuilder s = new StringBuilder(n);
for (int i = 0; i < n; i++) {
s.append("x");
}
// update button text with click count
// (convert to string, or else Android uses int as resource id!)
textview.setText(s);
}
}
package cs349.uwaterloo.ca.mvc1;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.ViewGroup;
public class MainActivity extends AppCompatActivity
{
// Private Vars
Model model;
/**
* OnCreate
* -- Called when application is initially launched.
* @see <a href="https://developer.android.com/guide/components/activities/activity-lifecycle.html">Android LifeCycle</a>
* @param savedInstanceState
*/
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Log.d(String.valueOf(R.string.DEBUG_MVC_ID), "onCreate called");
// Load base UI layout from resources.
setContentView(R.layout.activity_main);
// Init Model
model = new Model();
////////////////////////////////////////
// Setup Views
////////////////////////////////////////
View1 view1 = new View1(this, model);
ViewGroup v1 = (ViewGroup) findViewById(R.id.mainactivity_1);
v1.addView(view1);
View2 view2 = new View2(this, model);
ViewGroup v2 = (ViewGroup) findViewById(R.id.mainactivity_2);
v2.addView(view2);
// Init Views
model.initObservers();
}
////////////////////////////////////////////////////////////////////////////////////////////////
//
// Save and Restore State
//
////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Save the state of the application
* @param outState
*/
@Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
Log.d(String.valueOf(R.string.DEBUG_MVC_ID), "MainActivity: Save model state.");
// Serialize all sateful values form model
outState.putInt("Counter", model.getCounter());
}
/**
* This method is called after {@link #onStart} when the activity is
* being re-initialized from a previously saved state, given here in
* <var>savedInstanceState</var>. Most implementations will simply use {@link #onCreate}
* to restore their state, but it is sometimes convenient to do it here
* after all of the initialization has been done or to allow subclasses to
* decide whether to use your default implementation. The default
* implementation of this method performs a restore of any view state that
* had previously been frozen by {@link #onSaveInstanceState}.
* <p>
* <p>This method is called between {@link #onStart} and
* {@link #onPostCreate}.
*
* @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}.
* @see #onCreate
* @see #onPostCreate
* @see #onResume
* @see #onSaveInstanceState
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
Log.d(String.valueOf(R.string.DEBUG_MVC_ID), "MainActivity: Restore model");
// Get all values and restore them to model
model.setCounter(savedInstanceState.getInt("Counter"));
}
}
package cs349.uwaterloo.ca.mvc1;
import android.util.Log;
import java.util.Observable;
import java.util.Observer;
/**
* CS349_AndroidSamples
* <p>
* Created by J. J. Hartmann on 11/19/2017.
* Email: j3hartma@uwaterloo.ca
* Copyright 2017
*/
/**
* Class Model
* - Stores a persistent state for the application.
*/
public class Model extends Observable
{
// Private Variables
private int mCounter;
/**
* Model Constructor:
* - Init member variables
*/
Model() {
mCounter = 0;
}
/**
* Get mCounter Values
* @return Current value mCounter
*/
public int getCounter()
{
return mCounter;
}
/**
* Set mCounter Value
* @param i
* -- Value to set Counter
*/
public void setCounter(int i)
{
Log.d("DEMO", "Model: set counter to " + mCounter);
this.mCounter = i;
}
/**
* Increment mCounter by 1
*/
public void incrementCounter()
{
mCounter++;
Log.d("DEMO", "Model: increment counter to " + mCounter);
// Observable API
setChanged();
notifyObservers();
}
////////////////////////////////////////////////////////////////////////////////////////////////
//
// Observable Methods
//
////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Helper method to make it easier to initialize all observers
*/
public void initObservers()
{
setChanged();
notifyObservers();
}
/**
* Deletes an observer from the set of observers of this object.
* Passing <CODE>null</CODE> to this method will have no effect.
*
* @param o the observer to be deleted.
*/
@Override
public synchronized void deleteObserver(Observer o)
{
super.deleteObserver(o);
}
/**
* Adds an observer to the set of observers for this object, provided
* that it is not the same as some observer already in the set.
* The order in which notifications will be delivered to multiple
* observers is not specified. See the class comment.
*
* @param o an observer to be added.
* @throws NullPointerException if the parameter o is null.
*/
@Override
public synchronized void addObserver(Observer o)
{
super.addObserver(o);
}
/**
* Clears the observer list so that this object no longer has any observers.
*/
@Override
public synchronized void deleteObservers()
{
super.deleteObservers();
}
/**
* If this object has changed, as indicated by the
* <code>hasChanged</code> method, then notify all of its observers
* and then call the <code>clearChanged</code> method to
* indicate that this object has no longer changed.
* <p>
* Each observer has its <code>update</code> method called with two
* arguments: this observable object and <code>null</code>. In other
* words, this method is equivalent to:
* <blockquote><tt>
* notifyObservers(null)</tt></blockquote>
*
* @see Observable#clearChanged()
* @see Observable#hasChanged()
* @see Observer#update(Observable, Object)
*/
@Override
public void notifyObservers()
{
super.notifyObservers();
}
}
package cs349.uwaterloo.ca.mvc1;
import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import java.util.Observable;
import java.util.Observer;
/**
* CS349_AndroidSamples
* <p>
* Created by J. J. Hartmann on 11/19/2017.
* Email: j3hartma@uwaterloo.ca
* Copyright 2017
*/