Android’de Content Provider Kavramı ve Kullanımı #1

Mustafa Süleyman Kınık
7 min readMay 5, 2020

--

Merhabalar, bu yazımızda Content provider hakkında kısa bir bilgi edindikten sonra hep beraber öğrenmemizde etkili olabilecek bir uygulama yapacağız. Özellikle benim gibi öğrenci arkadaşlar için basit bir anlatım ile konunun kavranılması ve okuyan kişinin isteği takdirinde sonrasında dokümanlara ve daha detaylı yazılardan yararlanabilirler.

Content provider ismini ne zaman nasıl duydum hiç hatırlamıyorum lakin duyduktan sonra hep kendi kendime, şu yaptığım iş bitsin şu neymiş bir bak demeye başlamıştım. Kendilerini kurcalayıp anladıktan sonra ise “ya bu aslında güzel bir abimizmiş” demeden edemedim. Bugün ise burada yapacağımız uygulama fikri aynı zamanda benim bu platformdaki ilk yazım olacaktır. Arada sırada, uzun süredir kullanmadığım kavramları basit bir uygulama ile hafızamı tazelerken her seferinde şimdi değil, bunu sonra internet üzerinden paylaş diyerek hep bu tekrar çalışmalarının paylaşımını ertelemiştim. Nasip bugüneymiş hadi başlayalım o zaman.

Kısaca Content provider Nedir:

https://developer.android.com/guide/topics/providers/content-providers

Content provider’larımız uygulamamızda depolanan veya başka uygulamaların içinde depolanmış/kayıt edilmiş verilere güvenli bir şekilde erişebilme ve yönetebilme olanağı sağlayan bir yapıdır. Örnek olarak bugünde yapacağımız rehber uygulamasında görebileceğimiz gibi numaraların başka uygulamalardan erişimi ve paylaşımı veya numaraların kayıtlı olduğu veritabanının yönetiminin kolaylaştırdığını göreceğiz.

Content provider hakkında bu kadar kısaca bilgi vermek aslında kendisine hakaret olur lakin geri kalan kısmı, kodları yazarken uygulama üzerinde anlatmanın daha mantıklı olduğunu düşünüyorum. Eğer bu konu hakkında bilgiye doymak isterseniz buradan Google dokümantasyonlarına giderek çok ama çok fazla bilgiye ulaşabilirsiniz.

Rehber Uygulamamız

Yapacağımız projenin tamamına buradan erişebilirsiniz.

Yapacağımız bu uygulama telefonun rehberinden seçilen bir kayıtın yada kendimizin oluşturacağı bir girdi ile kayıt yapabildiğimiz ve bu kayıtlar üzerinde değişikliklere izin veren, kayıtların kendi kategorisi veya bütün hepsinin listelenmesine kullanıcının butonlar ile karar verebileceği özelliklere sahip olacaktır.

Yapacağımız uygulamadan bazı örnek resimler

Content Provider Oluşturuyoruz

Android Studio da projemizi oluşturuyoruz.

AppProvider isminde bir java class’ı projemize ekleyelim. Siz java class’ınızın ismini farklı yapabilirsiniz pek bir önemi yok. Daha sonrasında ContentProvider’ı AppProvider’ımıza extends edelim.

public class AppProvider extends ContentProvider

ve bizden implement etmemizi istediği her metodu ekliyoruz.

public class AppProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}

@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
return null;
}

@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}

@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
return null;
}

@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
return 0;
}

@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
return 0;
}
}

Burada daha önceden Web Api’ler ile uğraştıysanız gözünüze aşina gelen bir şeyler olacaktır. Mantık olarak yaptığımız implement işlemi CRUD işlemleri ile aynı işlevleri yapan metotları bize getirmiştir.(Bu projemizde getType metodu kullanılmayacaktır, bu metot anlatılan bilgilerin dahilinde olmamaktadır.)

Şimdi hiç ara vermeden AppProvider class’ımızın içine bir inner class olarak DBHelper class’ımızı oluşturup SQLiteOpenHelper’dan extends işlemini gerçekleştirelim.

public class DBHelper extends SQLiteOpenHelper

Bu işlem sonunda bizden istediği implement işlemini ve constructor mathing super işlemini alt+enter tuşuna basarak çıkan super class constructorlarından ilkini seçelim.

public class DBHelper extends SQLiteOpenHelper
{

public DPHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}

@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {

}

@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

}
}

Şimdi arkadaşlar neredeyse çoğu static final String tipinde olan sabitlerimizi ekleyelim ve tek tek ne olduğuna bakalım.

İlk başta static final kullanma sebebimizi açıklayalım. AppProvider class’ımızın hemen altında ve DBHelper inner class’ımızın hemen üstünde olan bu sabitlerimiz bizim projemizin diğer class’larından da hiçbir değişilikliğe maruz kalma tehlikesi yaşamadan erişebilmemiz için böyle yazılmıştır. Aynı zamanda, aslında yazılan bu String’lerin int’lerin yeri geldiğinde kendi elimizle yazabilme yetimiz olduğu gibi bunu böyle yazmak yerine static final tipinde ContentProvider’ın extends edildiği class’tan çekmek bu işin yazısız kuralı diyebiliriz.

AUTHORITY_NAME: Bizim package ismimizin sonuna class ismimizin eklenmiş halidir.

URL: AUTHORITY_NAME in content:// ön eki ve path yerine geçecek contacts son ekinin birleşimidir.Buradaki path kavramını internet sitelerindeki “site ismi /sayfa ismi” ile özdeşleştirebilirsiniz.

CONTET_URI: URL nin Uri formatına dönüştürülmüş halidir. Uri formatı bir internet sitesi linki/adresi olarak düşünebileceğimiz gibi daha genel olarak bir şeyin konumunu adresini belirten bir yapı olarak düşünebiliriz. Burada Uygulamamızda bulunan kayıtlı veriler için bir Uri oluşturmuş bulunmaktayız.

ID,TITLE,NAME,NUMBER: Veri tabanımızdaki column(sütun) isimlerini de , yapacağımız işi kuralına göre yapmak için static final içine atıp kullanılacak yerlerde bunları çağırarak kullanıyoruz.

URI_MATCHER: Bu kısmı yaparken insert içinde anlatacağım zira bizim projemize bir etkisi bulunmamaktadır.

Aynı yukarıda veritabanı column’ları için yaptığımız gibi veritabanı ile ilgili geri kalan isimleri(Create komutu dahil) static final biçiminde oluşturuyoruz.

static final String AUTHORITY_NAME ="com.example.contentproviderexample.AppProvider";
static final String URL="content://"+ AUTHORITY_NAME +"/contacts";
static final Uri CONTENT_URI=Uri.parse(URL);
static final String ID="id";
static final String TITLE="title";
static final String NAME="name";
static final String NUMBER="number";

static final UriMatcher URI_MATCHER;
static {
URI_MATCHER =new UriMatcher(UriMatcher.NO_MATCH);
URI_MATCHER.addURI(AUTHORITY_NAME,"contacts",1);
}


private SQLiteDatabase sqLiteDatabase;
static final String DATABASE_NAME="Contacts.db";
static final String TABLE_NAME="contacts";
static final int DATABASE_VERSION=1;
static final String CREATE_TABLE="CREATE TABLE "+TABLE_NAME+" (id INTEGER PRIMARY KEY AUTOINCREMENT,title TEXT NOT NULL, "+
" name TEXT NOT NULL,number INTEGER NOT NULL );";

Şimdi bunları yazdıktan sonra yavaştan işlerin derinlerine ineceğiz. İlk olarak DbHelper class’ımızın constructor’ı ile işlemler yapacağız.

public DBHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}

Constructor’a baktığımızda aslında version,name gibi dışardan alınan değerlerin tanıdık geldiğini fark ediyoruz. “name” kısmı bizim DATABASE_NAME kısmına karşılık gelmekte, “version” ise bizim DATABASE_VERSION kısmına karşılık gelmekte. Bizim şuanda projede static olarak tanımladığımız birer adet name ve version olduğundan zaten tek bir database üzerinden işlemler yapacağımızdan bu değerleri her seferinde constructordan almak yerine direk super metoduna ekliyoruz.

public DBHelper(@Nullable Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}

onCreate içine de SQLiteDatabase sınıfından türemiş olan değişkeni(şuanda bende otomatik olarak “sqLiteDatabase” ismi ile verilmekte ama sizde farklılık olabilir) kullanarak execSQL(CREATE_TABLE) yaparak tablomuzu oluştuşturuyoruz.

@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(CREATE_TABLE);

}

onUpgrade içinde ise önceki tablomuzu kayıttan düşürüp onCreate metodunu çağırarak tablo içindeki değişikliklerin güncellenmesini sağlıyoruz.

@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
sqLiteDatabase.execSQL("DROP TABLE IF EXISTS "+TABLE_NAME);
onCreate(sqLiteDatabase);

}

AppProvider class’ımızın onCreate metodunda ise DbHelper sınıfından bir object oluşturup devamında yapılan işlemlerle database ve tablo/tabloların oluşmasını/kurulmasını sağlıyoruz.

@Override
public boolean onCreate() {
DBHelper dbHelper=new DBHelper(getContext());
sqLiteDatabase=dbHelper.getWritableDatabase();
return sqLiteDatabase!=null;
}

Şimdi sıra okuma,kayıt etme,silme,güncelleme gibi işlevlerin yapıldığı metotlara sıra geldi.

query metodunun içindeki bilmediğimiz değişken adlarını açıklayarak başlayalım.(Not:Bu değişken adları sizde farklı bir şekilde isimlendirilmiş olabilir.)

String[] strings=Kısaca tablomuzdaki hangi sütunlar(column) ile işlem yapacağımızı belirtiyoruz.Biz null seçerek belirli column’lar yerine her birinin bize gelmesini sağlayacağız.

String s= En net ifadeyle sql deki where sorgusunu ifade ediyor.

String[] strings1= where sorgusunun yapılmasına sebep olan parametrelerin tutulduğu dizelerdir.

String s1= Listelenecek sonuçların hangi sıra ile sıralanacağını belirtiğimiz kısımdır.

SQLiteQueryBuilder sınıfı yardımı ile cursor’ımızın database içinde, database in hangi tablosunda hangi koşul-şartlara göre işlem yapacağımızı,metodumuzdan aldığımız değişken isimleri ile belirtiyoruz.Son olarak yaptığımız değişiklikler için setNotificationUri kullanıp, return cursor döndürüyoruz.

@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {


SQLiteQueryBuilder sqLiteQueryBuilder= new SQLiteQueryBuilder();
sqLiteQueryBuilder.setTables(TABLE_NAME);
Cursor cursor=sqLiteQueryBuilder.query(sqLiteDatabase,null,s,strings1,null,null,null);
cursor.setNotificationUri(getContext().getContentResolver(),uri);

return cursor;
}

Uri tipindeki insert metodumuzun içinde artık görmekten fazlasıyla aşina olduğumuz Uri ve ContentValues karşımıza çıkmakta.

Uri kısmını pas geçerek direk ContentValues hakkında bahsetmek gerekirse kendisi bizin için verileri ekleme,güncelleme işlemlerinde yardımcı olacaktır.

Tanımlamalarımızı yaptıktan sonra nihayetinde anlatmayı ertelediğim UriMatcher’a gelmiş durumdayız. En başlarda yaptığımız bu örnek uygulama için gerekli olmadığından bahsetmiştim. Bunun sebebi UriMatcher’ın bizim için, gelen urinin veri tabanındaki hangi tablo ile eşleşeceğini belirlemesidir. Bizim sadece tek bir tablomuz olduğundan böyle bir karar,organize etme mekanizması bu projede pekte yaramamaktadır. Yinede ben aşağıda ufak bir örneğini kullanmış durumdayım.

Aşağıda sizlerin fark ettiği gibi tablomuza contentValues’ler ile birlikte gelen değerleri kayıt ettiğimizi fark etmişsinizdir. Buraya kadar her şey normal. Fakat neden bunu long tipinde bir değeri eşitledik derseniz bunun sebebi, bizim bu yaptığımız kayıt işleminin unique bir adresi olmalı ki istediğimiz zaman ona ulaşabilirim. Bu kayıt işlemi sonrasında ise bize return olarak dönen long tipinde değeri withAppendedId işlemi ile yolun sonuna ekleyerek kayıt ettiğimiz değere ilerde ulaşabilmek için aslında bir adres yaratmış oluyoruz.

En son değişikliği bildirdikten sonra elde ettiğimiz Uri tipindeki değeri return ediyoruz.

@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
switch (URI_MATCHER.match(uri))
{
case 1:
long back=sqLiteDatabase.insert(TABLE_NAME,"",contentValues);
if(back>0)
{
Uri insertUri=ContentUris.withAppendedId(uri,back);
getContext().getContentResolver().notifyChange(insertUri,null);
return insertUri;
}

}
return null;
}

Şuana kadar yapılan işlemleri birazcık bile olsa anladıysak gayet iyi gidiyoruz demektir. Biraz kafamız karışık halde ise ileride oluşturacağımız yeni activity’ler ile birlikte orada yazacağımız kodlar ile birlikte konunun tamamen anlaşılacağına emin olabilirsiniz.

İlk olarak delete ile ilgilenelim. Bu metotta yaptığımız işlem TABLE_NAME ile verilen tablo isminin içindeki s ile verilen sütun(column) ismiyle where sorgusu/koşulu başlatıp strings dizisi ile içinde verdiğimiz değerleri barındıran satırları silme işlemi yapmaktayız. Yapılan işlem sonrası geriye bir int değer döndürüp, yapılan işlem değişikliğini bildirip bu döndürülen int değeri return etmekteyiz.

Güncelleme işlemini update isimli metot ile yapmaktayız. Yapılan işlemlerin insert metoduyla fazlasıyla benzer olduğu görülmekte. Aralarındaki tek fark güncelleme işleminin delete metodunda gördüğümüz gibi bizden bir sorgu/koşul yapmak için değerler istemesidir.

@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
int backDelete=sqLiteDatabase.delete(TABLE_NAME,s,strings);
getContext().getContentResolver().notifyChange(uri,null);

return backDelete;
}

@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {

int backUpdate =sqLiteDatabase.update(TABLE_NAME,contentValues,s,strings);

if(backUpdate>0)
{
getContext().getContentResolver().notifyChange(uri,null);
return backUpdate;

}
return 0;

}

En son olarakta AndroidManifest’imize provider’ımızı tanıtalım. Normalde bu işlem activity’ler için otomatik olarak oluşturulurken provider’larda kendimiz oluşturmalıyız.

<provider
android:authorities="com.example.contentproviderexample.AppProvider"
android:name=".AppProvider" />

Yazının 2. bölümüne aşağıdan ulaşabilirsiniz.

--

--