I am trying to better understand what it means to open an Sqlite database on a background thread in Android. Right now I am using a static/singleton pattern for my database via my class DatabaseHelper, so I only need to open it once, but I want to open it using good practice and understand why I shouldn't open it directly from within my Activity directly (or within the helper's constructor, for example).
My class:
public class DatabaseHelper extends SQLiteOpenHelper {
private static volatile SQLiteDatabase mDatabase;
private static DatabaseHelper mInstance = null;
private static Context mContext;
// ...
public static synchronized DatabaseHelper getInstance(Context context) {
/**
* use the application context as suggested by CommonsWare.
* this will ensure that you don't accidentally leak an Activity's
* context (see this article for more information:
* http://android-developers.blogspot.nl/2009/01/avoiding-memory-leaks.html)
*/
if (mInstance == null) {
mInstance = new DatabaseHelper(context.getApplicationContext());
}
return mInstance;
}
private DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
mContext = context;
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DB_CREATE_SOME_TABLE); //some SQL expression
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(DB_ALTER);
}
public void open() throws SQLException {
mDatabase = getWritableDatabase();
}
public void close() {
mDatabase.close();
}
public boolean isOpen() {
return mDatabase.isOpen();
}
//below this would be various CRUD functions operating on mDatabase
// ...
// ...
}
Is it correct to say that you should do something like this:
DatabaseHelper mDatabaseHelper = DatabaseHelper.getInstance(this);
Thread thread = new Thread("OpenDbThread") {
public void run(){
mDatabaseHelper.open();
}
};
thread.start();
inside an Activity somewhere?
You're correct that the code you wrote would open the database on a background thread. However, you wouldn't actually the database was opened until thread.isAlive() returned false (or mDatabase.isOpen() returned true). Alternatively, you could make your Activity listen for a callback from your Thread.
Related
I have the following DatabaseAccess.java class which contains the following code in order to instance , open , close and some queries :
public class DatabaseAccess {
Context context;
private SQLiteOpenHelper openHelper;
private SQLiteDatabase database;
private static DatabaseAccess instance;
/**
* Private constructor to avoid object creation from outside classes.
*
* #param context
*/
private DatabaseAccess(Context context) {
this.context = context;
this.openHelper = new DatabaseHelper(context);
}
/**
* Return a singleton instance of DatabaseAccess.
*
* #param context the Context
* #return the instance of DabaseAccess
*/
public static DatabaseAccess getInstance(Context context) {
if (instance == null) {
instance = new DatabaseAccess(context);
}
return instance;
}
/**
* Open the database connection.
*/
public void open() {
this.database = openHelper.getWritableDatabase();
}
/**
* Close the database connection.
*/
public void close() {
if (database != null) {
this.database.close();
}
}
However , im getting a serious SONAR warning about this :
Do not place Android context classes in static fields (static
reference to DatabaseAccess which has field context pointing to
Context); this is a memory leak
In my app , every time I need to use databaseAccess instance I do the following on onViewCreated()
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
databaseAccess = DatabaseAccess.getInstance(getContext());
}
I'm really confused about this , how can I fix this memory leak ?
Your getting the error because DatabaseAccess is created with context. And you have it static, so that object is never collected the Garbage. Hence the error.
Instead of passing activity context to the singleton class, you can pass applicationContext(). and #supress the warning
Firstly create an application class for your project like this.
<application android:name="com.xyz.YourApplicataionClassName">
</application>
Then in this application class, create a static getter method for your context.
Call this method wherever you need a context.
public class YourApplicataionClassName extends Application {
private static Context context;
public void onCreate() {
super.onCreate();
YourApplicataionClassName.context = getApplicationContext();
}
public static Context getAppContext() {
return YourApplicataionClassName.context;
}
}
Call YourApplicataionClassName.getAppContext() method and this returns your context.
However using Room Persistence Library is better way then doing all this operations
Have a look at the developer guidelines for implementing databases within Android here. The issue is that you save the context which is a potential memory leak, mentioned by TDIScott. Here is an example how to create it taken from the guidelines:
public class FeedReaderDbHelper extends SQLiteOpenHelper {
// If you change the database schema, you must increment the database version.
public static final int DATABASE_VERSION = 1;
public static final String DATABASE_NAME = "FeedReader.db";
public FeedReaderDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_ENTRIES);
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// This database is only a cache for online data, so its upgrade policy is
// to simply to discard the data and start over
db.execSQL(SQL_DELETE_ENTRIES);
onCreate(db);
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
onUpgrade(db, oldVersion, newVersion);
}
}
If you want a singleton, than do not store the context.
However, I would not recommend to use the SQL Lite implementation directly. ORM libraries or Room specifically are better ways to store data rather than writing the complete boilerplate code around that by yourself. You will find everything you need here.
I found many stuff like close the connection and close the cursor, but I do all this stuff. Still the SQLite connection leaks and I get a warning like this:
A SQLiteConnection object for database was leaked!
I have a database manager this, which I call in my activities with the following code:
DatabaseManager dbm = new DatabaseManager(this);
The code of my database manager class follows now:
public class DatabaseManager {
private static final int DATABASE_VERSION = 9;
private static final String DATABASE_NAME = "MyApp";
private Context context = null;
private DatabaseHelper dbHelper = null;
private SQLiteDatabase db = null;
public static class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
//create database tables
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//destroy and recreate them
}
}
public DatabaseManager(Context ctx) {
this.context = ctx;
}
private DatabaseManager open() throws SQLException {
dbHelper = new DatabaseHelper(context);
db = dbHelper.getWritableDatabase();
if (!db.isReadOnly()) {
db.execSQL("PRAGMA foreign_keys = ON;");
}
return this;
}
private void close() {
dbHelper.close();
}
}
When I call a database method, I do the following thing:
public Object getData() {
open();
//... database operations take place ...
close();
return data;
}
But as I said, I still get this SQLite connection leaked warning.
What am I doing wrong?
The bolded font in the citation corresponds to this part in your code:
private DatabaseManager open() throws SQLException {
dbHelper = new DatabaseHelper(context);
db = dbHelper.getWritableDatabase();
from: http://www.androiddesignpatterns.com/2012/05/correctly-managing-your-sqlite-database.html
Approach #1: Use an Abstract Factory to Instantiate the
SQLiteOpenHelper
Declare your database helper as a static instance variable and use the
Abstract Factory pattern to guarantee the singleton property. The
sample code below should give you a good idea on how to go about
designing the DatabaseHelper class correctly.
The static factory getInstance method ensures that only one
DatabaseHelper will ever exist at any given time. If the mInstance
object has not been initialized, one will be created. If one has
already been created then it will simply be returned.
You should
not initialize your helper object using with new DatabaseHelper(context).
Instead, always use
DatabaseHelper.getInstance(context), as it guarantees that only one
database helper will exist across the entire application's lifecycle.
public static class DatabaseHelper extends SQLiteOpenHelper {
private static DatabaseHelper mInstance = null;
private static final String DATABASE_NAME = "database_name";
private static final String DATABASE_TABLE = "table_name";
private static final int DATABASE_VERSION = 1;
public static DatabaseHelper getInstance(Context ctx) {
// Use the application context, which will ensure that you
// don't accidentally leak an Activity's context.
// See this article for more information: http://bit.ly/6LRzfx
if (mInstance == null) {
mInstance = new DatabaseHelper(ctx.getApplicationContext());
}
return mInstance;
}
/**
* Constructor should be private to prevent direct instantiation.
* make call to static factory method "getInstance()" instead.
*/
private DatabaseHelper(Context ctx) {
super(ctx, DATABASE_NAME, null, DATABASE_VERSION);
}
}
The complete example of the above-accepted answer:
It may help someone.
Helper Class:
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "sample.db";
private static final int DATABASE_VERSION = 1;
private static DatabaseHelper mInstance;
private DatabaseHelper(#Nullable Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public static synchronized DatabaseHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new DatabaseHelper(context.getApplicationContext());
}
return mInstance;
}
#Override
public void onCreate(SQLiteDatabase db) {
// create table stuff
}
#Override
public void onUpgrade(SQLiteDatabase db, int i, int i1) {
// drop table stuff
onCreate(db);
}
}
Activity:
SQLiteDatabase database = DatabaseHelper.getInstance(getApplicationContext()).getWritableDatabase();
Cursor cursor = database.query("query");
if (cursor != null) {
while (cursor.moveToNext()) {
// stuff
}
cursor.close();
database.close();
}
private void method() {
Cursor cursor = query();
if (flag == false) { // WRONG: return before close()
return;
}
cursor.close();
}
Good practice should be like this:
private void method() {
Cursor cursor = null;
try {
cursor = query();
} finally {
if (cursor != null)
cursor.close(); // RIGHT: ensure resource is always recovered
}
}
I need a suggestions how to achieve something in android and Java. I have a big project which has more than 50 activities and I use two different database classes to query sqlite statements and retrieve information from my system and user database. Here is an example how I am using and initializing my database :
SystemDatabaseHelper dbHelper = new SystemDatabaseHelper(this, null, 1);
dbHelper.initialize(this);
I am doing that in activity and the last few days I read a lot for memory leaks in android and the whole information about giving Context to a non-activity classes and the leaks which this can cause. My question is which is the best way to create some class and initialize it only from main activity and than use it in all other activities without initializing it again and again.
Any suggestions which is the best way to achieve this...i have some ideas,but want to hear your suggestions and best practices.
if you are trying to initialize your database helper class only once you're looking for singleton right?? here is an example how you can make it
public class ContactDBHelper extends SQLiteOpenHelper {
private ContactDBHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
}
private static ContactDBHelper mInstance;
public static synchronized ContactDBHelper getInstance() {
if (mInstance == null) {
mInstance = new ContactDBHelper(Util.getApplicationContext(),
ContactDB.DB_NAME, null, ContactDB.DB_VERSION);
}
return mInstance;
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(ContactDB.Contact.CREATE_STMT);
db.execSQL(ContactDB.Contact.CREATE_PHONE_NUMBER_INDEX);
db.execSQL(ContactDB.Contact.CREATE_REVERSE_PHONE_NUMBER_INDEX);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
}
#Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}
if you call ContactDBHelper.getInstance(); the first time , it will initialize the instance , after that any application component or any method will call it , it wont initialize it will return the singleton initialized instance .
note : for the Util.getApplicationContext it is a static helper method that returns application context which is set in the main Activity .
for memory leaks , you can avoid or protect your app from it using WeakReference or SoftRerference
here is the Util class
public class Util {
private static WeakReference<Context> applicationContext;
public static Context getApplicationContext() {
return applicationContext.get();
}
public static void setApplicationContext(Context context) {
applicationContext = new WeakReference<Context>(context);
}
}
You could extend the application class and use a single global reference. This also has the advantage of not using an Activity context.
public class NameOfApp extends Application {
public static SQLiteDatabase db;
#Override
public void onCreate() {
super.onCreate();
try{
// this will create the database if required (e.g. new install or db deleted)
db=new Database(this.getBaseContext()).getWritableDatabase();
} catch (Exception e) {
// TODO add alert and quit
Log.e(TAG,"Error creating DB:" + e.getMessage());
Toast.makeText(getApplicationContext(), "Error creating DB:" + e.getMessage(), Toast.LENGTH_LONG).show();
}
} // onCreate
#Override
public void onTerminate(){
Log.d(TAG, "Application.onTerminate() database closed");
super.onTerminate();
savePreferences();
db.close();
}
public void closeDB(){
db.close();
Log.d(TAG,"Database closed on request");
}
protected SQLiteDatabase getwritableDatabase(){
return db;
}
}
Use MyAppName.getWritableDatabase().
i yet really grasp this whole context thing we found a lot in android programming. so i tried creating a function to drop all my tables, and here's a my partial code:
public class DBAdapter {
private static class DbHelper extends SQLiteOpenHelper {
private boolean databaseCreated = false;
public DbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public void deleteTables(){
Log.d("DBAdapter","dlm drop tables pre");
this.sqlDatabase.execSQL("DROP TABLE IF EXISTS ["+TABLE_TV+"];");
this.sqlDatabase.execSQL("DROP TABLE IF EXISTS ["+TABLE_CAMERA+"];");
this.sqlDatabase.execSQL("DROP TABLE IF EXISTS ["+TABLE_GPS+"];");
}
}
}
and the part where i'm going to call the function deleteTables
public class UpdateDatabase {
public void updateTable(String table,JSONObject jsonObject){
DBAdapter db = new DBAdapter(this);
db.deleteTables();
}
}
but of course it will return an error, since DBAdapter expects a context. public class UpdateDatabase is not an activity class. Calling DbAdapter db = new DBAdapter(this) from activity class will work just find. So how do I find any fix for this problem?
thanks
You can add a constructor to UpdateDatabase that takes a Context and stores it so that it is available to be used by updateTable. Something like this:
public class UpdateDatabase {
private final Context mContext;
public UpdateDatabase(Context context){
mContext = context;
}
public void updateTable(String table,JSONObject jsonObject){
DBAdapter db = new DBAdapter(mContext);
db.deleteTables();
}
}
Now, whenever you do new UpdateDatabase() you will need to do new UpdateDatabase(..context..) instead. If you are doing this from an Activity, then you can do new UpdateDatabase(this).
hi look this code..
public class DbManager
{
// the Activity or Application that is creating an object from this class.
Context context;
CustomSQLiteOpenHelper helper;
// a reference to the database used by this application/object
protected SQLiteDatabase db;
private static DbManager INSTANCE;
// These constants are specific to the database.
protected final String DB_NAME = "yourDB";
protected final int DB_VERSION = 1;
public DbManager(Context context)
{
this.context = context;
// create or open the database
helper = new CustomSQLiteOpenHelper(context);
this.db = helper.getWritableDatabase();
}
public static DbManager getInstance(Context context){
if(INSTANCE == null)INSTANCE = new DbManager(context);
return INSTANCE;
}
public void db_Close()
{
if(helper!=null){
helper.close();
}
this.db.close();
}
private class CustomSQLiteOpenHelper extends SQLiteOpenHelper
{
public CustomSQLiteOpenHelper(Context context)
{
super(context, DB_NAME, null, DB_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db)
{
// This string is used to create the database.
// execute the query string to the database.
//db.execSQL(newTableQueryString);
Log.i("DataBaseManager", "Create Table");
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
// NOTHING TO DO HERE. THIS IS THE ORIGINAL DATABASE VERSION.
// OTHERWISE, YOU WOULD SPECIFIY HOW TO UPGRADE THE DATABASE.
}
}
}
// Inherit the DbManager Class
public class DataCollection extends DbManager {
public DataCollection(Context context){
super(context);
}
public void deleteTable(String TABLE_NAME){
try {db.execSQL("DROP TABLE "+TABLE_NAME);}//.delete(TABLE_NAME, null, null);}
catch (Exception e){
Log.e("DB ERROR", e.toString());
e.printStackTrace();
}
}
I've implemented access to a database using SQLiteOpenHelper from the android.database package within some classes (with pattern DAO).
I wrote some junit tests for these classes using an AndroidTestCase but this causes the tests to use the same database as the application.
I read that the ProviderTestCase2 or RenamingDelegatingContext can be used to test the database separately. Unluckily I couldn't find any nice tutorial/example that shows how to test a database with ProviderTestCase2/RenamingDelegatingContext.
Can anyone point me somewhere OR give me some tip OR share some code for database testing?!
Cheeerrrrsss!!
Giorgio
Both the ProviderTestCase and RenamingDelegatingContext will destroy the database if one already exists before opening it within it's context, so in that sense they both have the same low-level approach towards opening a SQLite database.
You leverage this by opening the database in your fixture in setUp(), which will then ensure that your working with a fresh database before each test case.
I would suggest that you go for writing content providers rather than creating database adapters. You can use a common interface for accessing data, be it stored in the DB or somewhere over the network, the design of content providers can be accommodated to access such data at the cost of a bit of IPC overhead involved that most of us shouldn't have to care about.
If you did this for accessing a SQLite database, the framework would completely manage the database connection for you in a separate process. As added beef, the ProviderTestCase2<ContentProvider> completely bootstraps a test context for your content provider without you having to a write a single line of code.
But, that's not said it isn't such a huge effort to do the bootstrapping yourself. So supposing you had a database adapter as such; we'll just focus on open() for getting write access to our database, nothing fancy:
public class MyAdapter {
private static final String DATABASE_NAME = "my.db";
private static final String DATABASE_TABLE = "table";
private static final int DATABASE_VERSION = 1;
/**
* Database queries
*/
private static final String DATABASE_CREATE_STATEMENT = "some awesome create statement";
private final Context mCtx;
private SQLiteDatabase mDb;
private DatabaseHelper mDbHelper;
private static class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE_STATEMENT);
}
#Override
public void onUpgrade(SQLiteDatabase db, int a, int b) {
// here to enable this code to compile
}
}
/**
* Constructor - takes the provided context to allow for the database to be
* opened/created.
*
* #param context the Context within which to work.
*/
public MyAdapter(Context context) {
mCtx = context;
}
/**
* Open the last.fm database. If it cannot be opened, try to create a new
* instance of the database. If it cannot be created, throw an exception to
* signal the failure.
*
* #return this (self reference, allowing this to be chained in an
* initialization call)
* #throws SQLException if the database could be neither opened or created
*/
public MyAdapter open() throws SQLException {
mDbHelper = new DatabaseHelper(mCtx);
mDb = mDbHelper.getWritableDatabase();
return this;
}
public void close() {
mDbHelper.close();
}
}
Then you could write your test as such:
public final class MyAdapterTests extends AndroidTestCase {
private static final String TEST_FILE_PREFIX = "test_";
private MyAdapter mMyAdapter;
#Override
protected void setUp() throws Exception {
super.setUp();
RenamingDelegatingContext context
= new RenamingDelegatingContext(getContext(), TEST_FILE_PREFIX);
mMyAdapter = new MyAdapter(context);
mMyAdapter.open();
}
#Override
protected void tearDown() throws Exception {
super.tearDown();
mMyAdapter.close();
mMyAdapter = null;
}
public void testPreConditions() {
assertNotNull(mMyAdapter);
}
}
So what's happening here is that the context implementation of RenamingDelegatingContext, once MyAdapter(context).open() is called, will always recreate the database. Each test you write now will be going against the state of the database after MyAdapter.DATABASE_CREATE_STATEMENT is called.
I actually use database with SQLiteOpenHelper and I have a trick for testing.
The idea is to use standard on-file stored DB during the normal use of the app and an in-memory DB during tests. In this way you can use a clear DB for each test without insert/delete/update data in your standard DB. It works fine for me.
Keep in mind you can use in-memory database, just passing null as name of database file. This is clearly documented in the API documentation.
Advantages of using in-memory DB during tests is explained here:
https://attakornw.wordpress.com/2012/02/25/using-in-memory-sqlite-database-in-android-tests/
In my project I have the DBHelper class wich extends SQLiteHelper. As you can see, there are the standard methods. I simply added a constructor with two parameters. The difference is that when I call the super constructor, I pass null as DB name.
public class DBHelper extends SQLiteOpenHelper {
public static final int DATABASE_VERSION = 1;
public static final String DATABASE_NAME = "mydatabase.db";
public DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public DBHelper(Context context, boolean testMode) {
super(context, null, null, DATABASE_VERSION);
}
public void onCreate(SQLiteDatabase db) {
//create statements
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//on upgrade policy
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//on downgrade policy
}
}
Every "model" in the project extends DBModel that is an abstract class.
public abstract class DBModel {
protected DBHelper dbhelper;
public DBModel(Context context) {
dbhelper = new DBHelper(context);
}
//other declarations and utility function omitted
}
As discussed here: How can I find out if code is running inside a JUnit test or not?
there is a way to establish if you are running JUnit tests, simply searching in stack trace elements.
As a conseguence, I modified DBModel constructor
public abstract class DBModel {
protected DBHelper dbhelper;
public DBModel(Context context) {
if(isJUnitTest()) {
dbhelper = new DBHelper(context, true);
} else {
dbhelper = new DBHelper(context);
}
}
private boolean isJUnitTest() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
List<StackTraceElement> list = Arrays.asList(stackTrace);
for (StackTraceElement element : list) {
if (element.getClassName().startsWith("junit.")) {
return true;
}
}
return false;
}
//other declarations and utility function omitted
}
Note that
startsWith("junit.")
may be
startsWith("org.junit.")
in your case.
I have an application that uses a ContentProvider backed by an sqlite database to provide data to the application.
Let PodcastDataProvider be the actual dataprovider used by the application.
Then you can set up a test provider with something like the following:
public abstract class AbstractPodcastDataProvider extends ProviderTestCase2<PodcastDataProvider>{
public AbstractPodcastDataProvider(){
this(PodcastDataProvider.class, Feed.BASE_AUTH);
}
public AbstractPodcastDataProvider(Class<PodcastDataProvider> providerClass,
String providerAuthority) {
super(providerClass, providerAuthority);
}
public void setUp() throws Exception{
super.setUp();
//clear out all the old data.
PodcastDataProvider dataProvider =
(PodcastDataProvider)getMockContentResolver()
.acquireContentProviderClient(Feed.BASE_AUTH)
.getLocalContentProvider();
dataProvider.deleteAll();
}
}
to setup a test data provider that will be backed by a different database than the actual application.
To test the DAO, create another class which extends AbstractPodcastDataProvider and use the
getMockContentResolver();
method to get an instance of a content resolver that will use the test database instead of the application database.
private static String db_path = "/data/data/android.testdb/mydb";
private SQLiteDatabase sqliteDatabase = null;
private Cursor cursor = null;
private String[] fields;
/*
* (non-Javadoc)
*
* #see dinota.data.sqlite.IDataContext#getSQLiteDatabase()
*/
public SQLiteDatabase getSQLiteDatabase() {
try {
sqliteDatabase = SQLiteDatabase.openDatabase(db_path, null,
SQLiteDatabase.OPEN_READWRITE);
sqliteDatabase.setVersion(1);
sqliteDatabase.setLocale(Locale.getDefault());
sqliteDatabase.setLockingEnabled(true);
return sqliteDatabase;
} catch (Exception e) {
return null;
}
}
if you give the exact location of the sqlite db(in my case it's db_path), using the above method you can find-out whether it returns an sqlitedatabase or not.
A possible solution can be to open database using this method
myDataBase = SQLiteDatabase.openDatabase(DATABASE_NAME, null, SQLiteDatabase.OPEN_READWRITE);
And change database name in your tests. Here you can find some info about this method.