GTranslate

 

DBExample0

 

DBExample0The Initial Baseline App

♦ Sources: DBExample0.zip

This example establishes the starting form from which, incrementally, we are going to extend concepts in database organization, and add interface items, widgets, activities, etc., as we expand the implementation.

The first thing to do is to determine the outline of what we want the app to do.

In this example series, we’ll be working with a database of information about pet dogs, so we have a known set of things we’ll want to do within the activities that make up the app:

  • get a starting/central list of a existing dog records by name – MainActivity
  • view individual dog’s info – ViewDogActivity
  • add a dog – AddDogActivity
  • edit a dog’s info – EditDogActivity

 

 Example0.filetreeWe will also create the ability to delete a record, but that doesn’t require an activity separate from viewing, as it can be done from a menu item while viewing a single record.

For such deleting, or for adding a dog or editing its info, two menus are created that invoke or redirect the activity to support the desired action.

Editing a dog’s record is available either through that menu item, or by clicking on the dog’s name while viewing that dog’s information.

We start out with a database having a single table. This is a good starting point. Yet, although having a single table is the situation many managers and most clients will want (or even expect!), it is unrealistic for full application development. So, in further examples we’ll explore how to use additional tables and key values to relate them.

However, using a single table in this first example will show the basic set-up of all the examples. And it will ease those who have had limited database-development instruction or experience, into the concepts underlying the organization of data within larger, real-world applications.

 

Database

For device/DBMS independence, we will use what is often termed the “ADO” way of communicating with SQLite. This is in distinction to using the SQLHelper* classes and functionality that Android devices support. Although the SQLHelper* methodology is a bit friendlier because its methods hide direct SQL commands, it is Android-only, so not the best when your goal is cross-platform DBMS independence.

To store data about dogs, we will have four fields:

 

  • each record’s unique database-row id
  • the dog’s Name
  • any info we want to add about this pup, and
  • where we got the dog from.

 

 So, our entire database schema will be one simple table, with this SQLite creation string:

1

CREATE TABLE IF NOT EXISTS

2

     Dogs (

3

         _id INTEGER   PRIMARY   KEY   AUTOINCREMENT ,

4

         Name   TEXT   NOT   NULL UNIQUE ,

5

         Info   TEXT   NOT   NULL ,

6

         Source TEXT   NOT NULL DEFAULT 'no data'

7

     );

 

[ If you don’t recognize any of the tokens used here, refer to the SQLite docs:  http://www.sqlite.org/keyword_index.html .]

Because our backing store and our display fields are the same, this example will use instances of the class Dog during our CRUD cycles, and for loading display fields.

The Dog class’s first field is a long, and the remaining ones are strings. (The first field’s name is prefixed with “DB”, as a reminder that the DBMS itself will determine that value and maintain it.)

 

The class that reflects the table definition within the DBMS is held within its class-definition file, which looks like this:

1

// Dog.cs

2

namespace DBExample0

3

{

4

    public class Dog

5

    {

6

        public long   DB_id  { get; set; }

7

        public string Name   { get; set; }

8

        public string Info   { get; set; }

9

        public string Source { get; set; }

10

    }

11

}

12

 

 

Example0’s definition of the DBMS interface is:


1

// IDogRepository.cs

2

using System.Collections.Generic;

3

 

4

namespace DBExample0

5

{

6

    public interface IDogRepository

7

    {

8

        List<Dog> GetAllDogs();

9

 

10

        bool AddDog(string name, string info, string source);

11

 

12

        long GetRowCount(string tableName);

13

 

14

        bool UpdateDog(string dogName,  string updCol,   string updVal);

15

        bool UpdateDog(string pivotCol, string pivotVal, string updCol, string updVal);

16

 

17

        void DeleteDog(long rowID);

18

        void DeleteDog(string dogName);

19

        void DeleteDog(string pivotCol, string pivotVal);

20

    }

21

}

 

 

The two methods that include parameters starting with “pivot” use string formatting within the methods to provide generalized ways of calling into the DBMS.

Thus the three DeleteDog methods support, from top to bottom, deletion by row ID, deletion by the dog’s name, and deletion of all rows in which the pivot column has the specified string value.

Main.cs

The MainApp class holds a static field for the database name and a static instance of the interface into the database, a copy that it instantiates during its OnCreate processing. These are the only things it needs to do:

 

 

// Main.cs

 

1

using System;

 

2

using Android.App;

 

3

using Android.Runtime;

 

4

 

 

5

namespace DBExample0

 

6

{

 

7

    [Application]

 

8

    public class MainApp : Application

 

9

    {

 

10

        public static string DBName = Resource.String.app_name + ".db3";

11

 

 

12

        public static IDogRepository TheDogRepository { get; private set; }

13

 

 

14

        public MainApp(IntPtr javaReference, JniHandleOwnership transfer)

 

15

            : base(javaReference, transfer)

 

16

        {

 

17

        }

 

18

 

 

19

        public override void OnCreate()

 

20

        {

 

21

            base.OnCreate();

 

22

 

 

23

            TheDogRepository = new DogRepository();

24

        }

 

25

    }

 

26

}

 

27

 

 

 

The starting screen with pickable list: MainActivity.cs

The MainActivity class loads an internal list containing the local copy of the contents of the database.

This is OK for small databases, or for temporary extracts of remote databases, however, it can cause a large working footprint and memory/execution bottleneck if your data grows to the point that it takes longer to load (and for the OS to maintain) your local copy than it would take to make regular, smaller calls into the DBMS. In a real-world application, this would require work in designing and implementing the usable mix of local data storage vs. database round-tripping code.

In setting up the ViewDogActivity, the MainActivity class actually uses its override of OnResume() to load (or refresh) its local copy of the data.

The list displayed by this activity is setup during the OnResume() method, by creating a DogListAdapter and setting it as the object that will populate the list displayed in this activity.

An OnClick()  listener is setup using the lambda expression defined in lines 17 – 30. This will let the user click on the name of the dog (displayed in the list) and be taken to second activity that displays all the information we have for that dog.

 

// MainActivity.axml

1

<?xml version="1.0" encoding="utf-8"?>

2

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

3

    android:orientation="vertical"

4

    android:layout_width="fill_parent"

5

    android:layout_height="fill_parent">

6

    <ListView

7

        android:id="@+id/Dogs"

8

        android:layout_width="match_parent"

9

        android:layout_height="wrap_content" />

10

</LinearLayout>

 Example0.0

 

1

// MainActivity.cs

 

2

using System;

 

3

using System.Collections.Generic;

 

4

using Android.App;

 

5

using Android.Content;

 

6

using Android.Views;

 

7

using Android.Widget;

 

8

using Android.OS;

 

9

 

 

10

namespace DBExample0

 

11

{

 

12

    [Activity(Label = "DBExample0", MainLauncher = true, Icon = "@drawable/icon")]

 

13

    public class MainActivity : Activity

 

14

    {

 

15

        private  ListView  _dogListView;

 

16

        internal List<Dog> dogList;

 

17

 

 

18

        protected override void OnCreate(Bundle bundle)

 

19

        {

 

20

            base.OnCreate(bundle);

 

21

 

 

22

            SetContentView(Resource.Layout.MainActivity);

load current

23

 

   XML

24

            _dogListView = FindViewById<ListView>(Resource.Id.Dogs);

 

25

 

    setup for going

26

            _dogListView.ItemClick += (sender, args) =>

to single-row

27

            {

   display

28

                var dog = dogList[args.Position];

 

29

 

 

30

                var intent = new Intent(this, typeof(ViewDogActivity));

 

31

 

 

32

                // pass the values we'll need in the next activity

 

33

                intent.PutExtra("DB_id",  dog.DB_id);

 

34

                intent.PutExtra("Name",   dog.Name);

 

35

                intent.PutExtra("Info",   dog.Info);

 

36

                intent.PutExtra("Source", dog.Source);

 

37

 

 

38

                StartActivity(intent);

 

39

            };

 

40

        }

 

41

 

 

42

        protected override void OnResume()

 

43

        {

 

44

            base.OnResume();

 

45

 

 

46

            if(dogList != null//if this isn't the first time through here,

 

47

                dogList.Clear(); //  go ahead and remove the old records

 

48

                                 //  before we re-load with current values

 

49

 

     load the

50

            dogList = MainApp.TheDogRepository.GetAllDogs();

local copy

51

 

  send it to the

52

            _dogListView.Adapter = new DogListAdapter(this, dogList);

 display list-

 53

        }

  adapter

54

 

 

55

        public override bool OnCreateOptionsMenu(IMenu menu)

 

56

        {

 

57

            MenuInflater.Inflate(Resource.Menu.AddDogMenu, menu);

 

58

 

 

59

            returnbase.OnPrepareOptionsMenu(menu);

 

60

        }

 

61

 

 

62

        public override bool OnOptionsItemSelected(IMenuItem item)

 

63

        {

 

64

            if(item.ItemId == Resource.Id.AddDog)

 

65

            {

 

66

                StartActivity(typeof(AddDogActivity));

go to add

67

 

    activity

68

                return true;

 

69

            }

 

70

 

 

71

            return base.OnOptionsItemSelected(item);

 

72

        }

 

73

    }

 

74

}

 

 

TheDogRepository.cs implementation of IDogRepository:

The constructor of the DogRepository class sets up the two readonly fields in that class: it creates the string holding the full database path, and then uses it to set up the_dataSourcefield for the use of the private method _GetConnection(). Then it calls the private method _SetupDatabase(), which creates the database if it doesn’t exist, and does a reset of the database and clean initialization of it during debugging, if desired.

Data is read into instances of the Dog class using the SqliteDataReader class. The text-indices into rdr are the names of the columns, as given in the schema used to create the table.  One of the indexers within the SqliteDataReader class uses the name given to look up the value in the data obtained by its parent (DbDataReader) from the SELECT command.

 

 

1

// TheDogRepository.cs

 

2

using System;

 

3

using System.Collections.Generic;

 

4

using System.IO;

 

5

using Mono.Data.Sqlite;

 

6

using Android.Util;

 

7

using Android.Widget;

 

8

 

 

9

namespace DBExample0

 

10

{

 

11

    public class DogRepository : IDogRepository

 

12

    {

 

13

        #if DEBUG

 

14

        static int dbgInitializeData = 0;

 

15

        #endif

 

16

 

 

17

        private readonly string _databasePath;

 

18

        private readonly string _dataSource;

 

19

 

 

20

        public DogRepository()

 

21

        {

 

22

            _databasePath = 

 

23

                Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal),

 

24

                             MainApp.DBName);

 

25

            _dataSource   = "Data Source=" + _databasePath;

 

26

 

 

27

            _SetupDatabase();

 

28

        }

 

29

 

 

30

        private SqliteConnection _GetConnection()

 

31

        {

 

32

            try

 

33

            {

 

34

                return new SqliteConnection(_dataSource);

 

35

            }

 

36

            catch(SqliteException e)

 

37

            {

 

38

                Log.Debug("Could not create instance of SqliteConnection({0}): "

 

39

                          _dataSource, e.Message);

 

40

                return null;

 

41

            }

 

42

        }

 

43

 

 

44

        private bool _SetupDatabase()

 

45

        {

 

46

            if(!File.Exists(_databasePath))

 

47

            {

 

48

                Log.Debug("_SetupDatabase""CreateFile({0})", _databasePath);

 

49

 

 

50

                try

 

51

                {

 

52

                    SqliteConnection.CreateFile(_databasePath);

 

53

                }

 

54

                catch(Exception e) // if thrown, will come from File.Create call,

 

55

                {                  //   not from SQLite lib

 

56

                    Log.Debug("_SetupDatabase"

 

57

                              "SqliteConnection.CreateFile({0}) failed: {1}"

 

58

                              _databasePath, e.Message);

 

59

                    return false;

 

60

                }

 

61

 

 

62

            #if DEBUG

 

63

                dbgInitializeData = 1;

 

64

            #endif

 

65

 

 

66

            }

 

67

 

 

68

            // The following database accesses are set up so as not to fail, so

 

69

            //  they shouldn't need try/catch blocks

 

70

 

 

71

            #if DEBUG

 

72

            {

 

73

                // While debugging, set a breakpoint on this 'if' and change the value 

 

74

                //   of 'deleteExisting' to 1 before it's tested if you want to  

 

75

                //   reinitialize the database to known values, which is done below, 

 

76

                //   at next test of 'deleteExisting'.

 

77

 

 

78

                if(dbgInitializeData == 1)

 

79

                {

 

80

                    Log.Debug("_SetupDatabase""Deleting table definitions.");

 

81

                    using(var connection = _GetConnection())

 

82

                        using(var command = connection.CreateCommand())

 

83

                        {

 

84

                            connection.Open();

 

85

                            command.CommandText =

 

86

                                @"DROP TABLE IF EXISTS Dogs;";

 

87

 

 

88

                            command.ExecuteNonQuery();

 

89

                            connection.Close();

 

90

                        }

 

91

 

 

92

                    Log.Debug("_SetupDatabase""Done.");

 

93

                }

 

94

            }

 

95

            #endif

 

96

 

 

97

            Log.Debug("_SetupDatabase""Creating `Dogs` table.");

 

98

            using(var connection = _GetConnection())

 

99

                using(var command = connection.CreateCommand())

 

100

                {

 

101

                    connection.Open();

 

102

                    command.CommandText =

 

103

                    @"CREATE TABLE IF NOT EXISTS 

 

104

                        Dogs (

 

105

                            _id INTEGER PRIMARY KEY AUTOINCREMENT,

 

106

                            Name   TEXT NOT NULL UNIQUE,

 

107

                            Info   TEXT NOT NULL,

 

108

                            Source TEXT NOT NULL DEFAULT 'no data'

 

109

                        );";

 

110

 

 

111

                    command.ExecuteNonQuery();

 

112

                    connection.Close();

 

113

                }

 

114

 

 

115

            #if DEBUG

 

116

            {

 

117

                if(dbgInitializeData == 1)

 

118

                {

 

119

                    string[,] testDog = {

 

120

                        { "Maggie Bear""fawn Boxer female",     "breeder"       },

 

121

                        { "Jason",       "fawn Boxer male",       "Boxer Rescue"  },

 

122

                        { "Wicket",      "Brussels Griffon male""breeder"       },

 

123

                        { "Nyx",         "Chug male",             "Animal Rescue" }

 

124

                    };

 

125

 

 

126

                    for(int i = 0; i < testDog.GetLength(0); i++)

 

127

                        AddDog(testDog[i, 0], testDog[i, 1], testDog[i, 2]);

 

128

                }

 

129

            }

 

130

            #endif

 

131

 

 

132

            Log.Debug("_SetupDatabase""Done.");

 

133

            return true;

 

134

        }

 

135

 

 

136

        public List<Dog> GetAllDogs()

 

137

        {

 

138

            var dogList = new List<Dog>();

 

139

 

 

140

            Log.Debug("GetAllDogs""Retrieving.");

 

141

 

 

142

            using(var connection = _GetConnection())

 

143

                using(var command = connection.CreateCommand())

 

144

                {

 

145

                    connection.Open();

 

146

                    command.CommandText = "SELECT * FROM Dogs ORDER BY Name";

 

147

 

 

148

                    using(var reader = command.ExecuteReader())

 

149

                        while(reader.Read())

 

150

                            dogList.Add(_ReadDog(reader));

 

151

 

 

152

                    connection.Close();

 

153

                }

 

154

 

 

155

            Log.Debug("GetAllDogs"

 

156

                      "Success, with records: DB(n) = {0}; list(n) = {1}"

 

157

                      GetRowCount("Dogs"), dogList.Count);

 

158

 

 

159

            return dogList;

 

160

        }

 

161

 

 

162

        private Dog _ReadDog(SqliteDataReader rdr)

 

163

        {

 

164

            //retrieve values using column name as index

 

165

            Log.Debug("_ReadDog""_id = {0}",      (long)rdr["_id"]);

 

166

            Log.Debug("_ReadDog""Name = {0}",   (string)rdr["Name"]);

 

167

            Log.Debug("_ReadDog""Info = {0}",   (string)rdr["Info"]); 

 

168

            Log.Debug("_ReadDog""Source = {0}", (string)rdr["Source"]);

 

169

 

 

170

            return new Dog {

 

171

                DB_id  =   (long)rdr["_id"],

 

172

                Name   = (string)rdr["Name"],

 

173

                Info   = (string)rdr["Info"],

 

174

                Source = (string)rdr["Source"]

 

175

            };

 

176

        }

 

177

 

 

178

 

 

179

        public long GetRowCount(string tableName)

 

180

        {

 

181

            long rows = 0;

 

182

 

 

183

            string cmd = string.Format("SELECT COUNT(*) AS num FROM {0}",

 

184

                                       tableName);

 

185

 

 

186

            using(var connection = _GetConnection())

 

187

                using(var command = connection.CreateCommand())

 

188

                {

 

189

                    connection.Open();

 

190

 

 

191

                    command.CommandText = cmd;

 

192

                    var reader = command.ExecuteReader();

 

193

 

 

194

                    if(reader.Read())

 

195

                        rows = (long)Convert.ToInt32(reader["num"]);

 

196

 

 

197

                    connection.Close();

 

198

                }

 

199

 

 

200

            return rows;

 

201

        }

 

202

 

 

203

 

 

204

        public bool AddDog(string name, string info, string source)

 

205

        {

 

206

            Log.Debug("AddDog"

 

207

                      "CommandText: INSERT INTO Dogs (Name, Info, Source) "

 

208

                    + "VALUES ( {0}, {1}, {2} );",

 

209

                      name, info, source);

 

210

 

 

211

            string strCommand = 

 

212

                      "INSERT INTO Dogs ( Name, Info, Source ) "

 

213

                    + "VALUES ( @name, @info, @source );";

 

214

 

 

215

            try

 

216

            {

 

217

                using(var connection = _GetConnection())

 

218

                    using(var command = connection.CreateCommand())

 

219

                    {

 

220

                        connection.Open();

 

221

 

 

222

                        command.CommandText = strCommand;

 

223

                        command.Parameters.AddWithValue("@name",   name);

 

224

                        command.Parameters.AddWithValue("@info",   info);

 

225

                        command.Parameters.AddWithValue("@source", source);

 

226

 

 

227

                        command.ExecuteNonQuery();

 

228

 

 

229

                        connection.Close();

 

230

                    }

 

231

            }

 

232

            catch(SqliteException e)

 

233

            {

 

234

                Log.Debug("AddDog""SqliteException #{0} caught: {1}",

 

235

                          e.ErrorCode.ToString(), e.Message);

 

236

 

 

237

                // TODO: check the ErrorCode: if it's not a constraint error, 

*

238

                //          you’ll need to dig into it further!

 

239

 

 

240

                return false;

 

241

            }

 

242

 

 

243

            Log.Debug("AddDog""{0}: Success. New count = {1}", name, GetRowCount("Dogs"));

 

244

            return true;

 

245

        }

 

246

 

 

247

 

 

248

        public bool UpdateDog(string dogName, string updCol, string updVal)

 

249

        {

 

250

            return UpdateDog("Name", dogName, updCol, updVal);

 

251

        }

 

252

 

 

253

 

 

254

        //WARNING: if 'pivotCol' is not UNIQUE, this will update ALL the 

 

255

        //          rows that match 'pivotVal'!

 

256

        public bool UpdateDog(string pivotCol, string pivotVal, 

 

257

                              string updCol,   string updVal)

 

258

        {

 

259

            Log.Debug("UpdateDog"

 

260

                      "CommandText: UPDATE Dogs SET {0} = '{1}' WHERE {2} = '{3}';",

 

261

                      updCol, updVal.ToString(), pivotCol, pivotVal.ToString());

 

262

 

 

263

            string strCommand = 

 

264

                    string.Format("UPDATE Dogs "

 

265

                                + "SET   {0} = @updValue "

 

266

                                + "WHERE {1} = @pivotValue;",

 

267

                                  updCol, pivotCol);

 

268

 

 

269

            try

 

270

            {

 

271

                using(var connection = _GetConnection())

 

272

                    using(var command = connection.CreateCommand())

 

273

                    {

 

274

                        connection.Open();

 

275

 

 

276

                        command.CommandText = strCommand;

 

277

                        command.Parameters.AddWithValue("@updValue",    updVal);

 

278

                        command.Parameters.AddWithValue("@pivotValue",  pivotVal);

 

279

 

 

280

                        command.ExecuteNonQuery();

 

281

 

 

282

                        connection.Close();

 

283

                    }

 

284

            }

 

285

            catch(SqliteException e)

 

286

            {

 

287

                Log.Debug("UpdateDog""SqliteException #{0} caught: {1}"

 

288

                          e.ErrorCode.ToString(), e.Message);

 

289

 

 

290

                // TODO: check the ErrorCode: if it's not a constraint error, 

*

291

                //          you’ll need to dig into it further!

 

292

 

 

293

                return false;

 

294

            }

 

295

 

 

296

            Log.Debug("UpdateDog""Success.");

 

297

            return true;

 

298

        }

 

299

 

 

300

        public void DeleteDog(long id)

 

301

        {

 

302

            DeleteDog("_id", id.ToString());

 

303

        }

 

304

 

 

305

        public void DeleteDog(string name)

 

306

        {

 

307

            DeleteDog("Name", name);

 

308

        }

 

309

 

 

310

        //WARNING: if pivotCol is NOT defined in the schema as UNIQUE, this method

 

311

        //          will delete all rows in which the pivotCol has the pivotVal!

 

312

        //This example is here to show how you can use string formatting on the command

 

313

        //  value to do things that you need to tailor to your application for speed,

 

314

        //  efficiency or clarity.

 

315

        public void DeleteDog(string pivotCol, string pivotVal)

 

316

        {

 

317

            Log.Debug("DeleteDog"

 

318

                      "CommandText: DELETE FROM Dogs WHERE {0} = {1};"

 

319

                      pivotCol, pivotVal);

 

320

 

 

321

            string strCommand = 

 

322

                      string.Format("DELETE FROM Dogs WHERE {0} = @pivotValue;"

 

323

                                    pivotCol);

 

324

 

 

325

            using(var connection = _GetConnection())

 

326

                using(var command = connection.CreateCommand())

 

327

                {

 

328

                    connection.Open();

 

329

 

 

330

                    command.CommandText = strCommand;

 

331

                    command.Parameters.AddWithValue("@pivotValue",  pivotVal);

 

332

 

 

333

                    command.ExecuteNonQuery();

 

334

 

 

335

                    connection.Close();

 

336

                }

 

337

 

 

338

            Log.Debug("DeleteDog""Success.");

 

339

        }

 

340

    }

 

341

}

 

 

 

 

 

* A “constraint error” is one arising from enforcement of a setting within the table definition, e.g., UNIQUE, etc.

 

 

 

 

As will be done in all the examples, this file contains all the DBMS interface code that the example app uses.

It is vital that you segregate all database accesses within this class in order to make sure that your ability to use the interface definition across platforms remain viable!

The Processes:

1. List of dogs, going to display of existing individual data

Data is read into instances of the Dog class using the SqliteDataReader class. The text-indices into rdr are the names of the columns, as given in the schema used to create the table.  One of the indexers within the SqliteDataReader class uses the name given to look up the value in the data obtained by its parent (DbDataReader) from the SELECT command.

The list of dog information is passed to the list adapter for the main activity and used to display the list of dog names.

If a dog’s name is selected from the list by tapping on it, the associated information is passed into the new intent, then that intent is used to fire off the activity (ViewDogActivity) that displays sets of individual dog’s data.

Example0.1        Example0.2

 

2. Individual data display, going into editing it

The example code for this activity demonstrates two ways of starting a new activity: by choosing a menu item or by tapping a text field.

While an individual dog’s data set is on the screen in the ViewDogActivity, a new intent will be loaded with the appropriate data from the current activity, and the EditDogActivity started either by bringing up the menu and choosing “Edit” or by tapping the dog-name itself. (Yes, that second way of invocation is a non-intuitive UI design, but that’s not the topic of these examples.)

Within this iteration of the example, all information held is in the form of a string, and editable during this activity as text fields.

The code behind the activity does checks of the schema requirements, for UNIQUE and NOT NULL columns, in particular. These constraints can be checked for either pre-emptively in app code, or by catching exceptions thrown by the DBMS. (For reference, these examples switch between the two methodologies: each has its costs and benefits.)

Example0.3

3. List of dogs display, going into adding a record

The (main) activity, which displays a selectable list of dog names also has a menu that the user can bring up. The sole choice is “Add a Dog”.

Selection of this option starts the AddDogActivity, which allows the user to do exactly that.

This activity present empty text fields for data entry. The user can tap the “Save” button when information-entry has been completed, or press the ‘back’ button to cancel the addition.

If “Save” is chosen, the data is put through schema-consistency checks that parallel those performed after the user’s having edited an existing record. One difference between the "Add" activity and the "Edit" activity is that the Edit screen has a "Cancel" button (and its listener), while the Add activity relies on the "back" button for cancelling the addition. Current applications use either method; it's best to chose one for your application and use it consistently.

Example0.0.withAddMenu         Example0.AddADog

 

And that’s the limit of activities that these examples are set up to do. They’re simple, but allow us to extend the data model, or the activities surrounding the input or editing of data, within a clear, minimalist framework.

 

NEXT

 

 

Site design and contents (other than where otherwise noted)
Copyright © Eduard Qualls, 2013 - 2016.