13 Şubat 2018 Salı

Android Dersleri 6 - Content Provider nedir?


6 - Content Provider

Android uygulamaları birbirlerinin verilerine doğrudan ulaşamazlar. Bu, hem güvenlik hem de uygulamaların birbirinden soyutlanabilmesini sağlar. Bir veriyi birden fazla uygulamada kullanabilmek için "Content Provider" kullanılır.
https://www.tutorialspoint.com/android/android_content_providers.htm
        A content provider component supplies data from one application to others on request.( Bir uygulamanın verilerine diğer uygulamalardan erişebilmek için content provider kullanılmalıdır. Örneğin A uygulaması için bir content provider tanımladık diyelim, bu content provider sayesinde A uygulamasının verilerine diğer uygulamalardan erişebiliriz. )
        Such requests are handled by the methods of the ContentResolver class. (Diğer uygulamalar, A uygulamasının content provider'ına istek yapmak için ContentResolver class'ını kullanır. )
        A content provider can use different ways to store its data and the data can be stored in a database, in files, or even over a network. ( A uygulaması için bir content provider tanımlayacağız. Diğer uygulamalar A uygulamasının verilerine bu content provider'ı kullanarak erişebilecekler. Bu content provider, A uygulamasının verilerini saklamak, hafızada tutmak için farklı yollar kullanabilir, A uygulamasının verileri bir veritabanında, dosyada veya bir network üzerinde bir yerde tutuluyor olabilir. )
        Sometimes it is required to share data across applications. This is where content providers become very useful.( Content provider, uygulamalar arasında veri paylaşımını sağlar. Örneğin,Rehber uygulaması ve WhatsApp uygulaması arasındaki veri paylaşımın content provider sağlar. )
        Content providers let you centralize content in one place and have many different applications access it as needed. ( Content provider sayesinde bir uygulmanın verilerine diğer uygulamalar'ın erişebilmesini sağlamış oluruz.  )
        A content provider behaves very much like a database where you can query it, edit its content, as well as add or delete content using insert(), update(), delete(), and query() methods. ( Bir content provider, bir veri tabanına benzer. Nasıl ki bir veritabanına sorgulayabilirsek(select sorgusu atabilirsek), veritanının içeriğini değiştirebilirsek, veritabanına yeni bir kayıt ekleyebilir veya varolan bir kaydı silebilirsek, benzer şekilde aynı işlemleri content provider ile de yapabiliriz.
        Yani content provider'a select sorgusu atarak bir uygulamanını verilerine erişebiliriz, content provider'ı kullanarak bir uygulamadaki verilerin içeriğini değiştirebiliriz, content provider'ı kullanarak bir uygulamanın verilerine yeni bir veri ekleyebiliriz veya silebiliriz. )
        In most cases this data is stored in an SQlite database. ( Çoğu case'de, content provider'ı kullanarak erişeceğimiz uygulama verisi SQLite veritabanında saklanır. )
        A content provider is implemented as a subclass of ContentProvider class and must implement a standard set of APIs that enable other applications to perform transactions. ( Bir content provider, ContentProvider class'ının subclass'ıdır.  )

public class My Application extends  ContentProvider
{
}
( Varolan content provider class'ları bu şekilde tanımlıdır. Kendi ContentProvider class'ımızı tanımlamak istersek de bu şekilde ContentProvider'ı extend eden bir class tanımlarız. )

Content URIs

        To query a content provider, you specify the query string in the form of a URI which has following format: <prefix>://<authority>/<data_type>/<id>
 ( Bir content provider'a sorgu atmak için URI sentax'ını yani şu sentax'ı kullanırız : <prefix>://<authority>/<data_type>/<id> . URI syntax'ı 4 kısımdan oluşur. Şimdi bunları inceleyelim: )
prefix :     This is always set to content:// ( 1. kısım prefix'dir, bu kısıma genellikle content:// yazılır. )
authority :        This specifies the name of the content provider, for example contacts, browser etc. For third-party content providers, this could be the fully qualified name, such as com.tutorialspoint.statusprovider. (2. kısma authority denir. Authority kısmında sorgulamak istediğimiz content provider'ın ismini yazarız. Örneğin sorgulamak istediğimiz content provider'ın ismi şunlardan biri olabilir : contacts, browser etc. Bunlar telefonda default olarak bulunan content provider'lardır. Third-party content provider'lar için, mesela kendi content provider class'ımızı tanımladık diyelim bu content provider'a sorgu atmak istiyorsak, URI syntax'ının authority kısmına bu content provider class'ının fully qualified name'ini, örneğin com.tutorialspoint.statusprovider yazmamız tavsiye edilmiştir.
        Authority'ye kendi tanımladığımız provider class'ının full path'ini vermemiz tavsiye edilir. Ancak bu zorunlu değildir başka bir isim de verebiliriz authority'ye ancak bu isim unique olmalıdır. Authority'nin unique olmasını sağlamak için şirketimizin domain ismi + provider class ismini authority değeri olarka kullanabiliriz. Manifest.xml dosyasında, <application> tag'ı altında <provider> tag'ının android:authority attribute'üne authority değerini veririz. Ayrıca, <provider> tag'ının android:name attribute'üne de tanımladığımız provider class'ının full path'İni veririz. authority'yi content provider'ın mantıksal ismi, dışarıdan görünen ismi gibi düşünebilirsin. URI'da geçen authority'nin hangi provider class'ına map edeceğini işte manifest.xml dosyasına yazdığımız provider element ile belirleriz. provider element'İnin authority attribute'üne istediğimiz bir unique isim verebiliriz, başka uygulamalar bu unique ismi kullanarak bizim content provider'ımıza erişirler. provider element'inin name attribute'üne ise provider class'ının path'i karşılık gelir.  )
data_type :       This indicates the type of data that this particular provider provides. For example, if you are getting all the contacts from the Contacts content provider, then the data path would be people and URI would look like this content://contacts/people ( Sorgulamak istediğimiz content provider'daki hangi type'daki verileri sorgulamak istediğimizi URI form'unun 3.kısmında yani data_type kısmında yazarız. Örnek : Contacts isimli content provider'daki people type'ındaki tüm kayıtları sorgulamak istiyorsak URI form'unu şöyle yazmalıyız : content://contacts/people böylece rehberde kayıtlı tüm kayıtlar sorgulanır select * from contacts sorgusu atmışız gibi düşün. )
id :   This specifies the specific record requested. For example, if you are looking for contact number 5 in the Contacts content provider then URI would look like this content://contacts/people/5. ( Content provider'daki belirli bir unique id'ye sahip olan bir kaydı getirmek,elde etmek istiyorsak, URI form'unun 4.kısmında yani id kısmında unique id'yi yazarız. Örnek : content://contacts/people/5 )

Create Content Provider

https://www.tutorialspoint.com/android/android_content_providers.htm
        This involves number of simple steps to create your own content provider. (Kendi content provider class'ımızı tanımlamak istiyorsak, şu adımları izmeliyiz.)
        First of all you need to create a Content Provider class that extends the ContentProviderbaseclass. ( ContentProvider class'ının extend eden bir class tanımlarız. )
        Second, you need to define your content provider URI address which will be used to access the content. ( Uygulamamızdaki verilere erişmek için kullanılacak olan content provider'ın URI addres'ini tanımlarız. )
        Next you will need to create your own database to keep the content. Usually, Android uses SQLite database and framework needs to override onCreate() method which will use SQLite Open Helper method to create or open the provider's database. When your application is launched, the onCreate() handler of each of its Content Providers is called on the main application thread.( Uygulamamızdaki verileri saklamak, tutmak için kendi veritabanımızı yaratırız bunun için genellikle SQLite database kullanılır. )
        Next you will have to implement Content Provider queries to perform different database specific operations.
        Finally register your Content Provider in your activity file using <provider> tag. ( Tanımladımız Content Provider class'ını <provider> tag'ını kullanarak register etmeliyiz ama nasıl anlamadım? )

        Here is the list of methods which you need to override in Content Provider class to have your Content Provider working: onCreate(), query(), insert(), delete(), update(), getType() . Let's study these methods :  ( Tanımladımız Content Provider class'ında şu method'ları override etmeliyiz. Bu method'lar base method'umuz olan ContentProvider'dan gelir, ContentProvider'ın subclass'larında override edilmelidirler : onCreate(), query(), insert(), delete(), update(), getType(). Hadi Bu method'ları inceleyelim. )
onCreate() : This method is called when the provider is started.( Provider çalışmaya başladığında otomatik olarak onCreate() method'u çalışır. )
query() : This method receives a request from a client. The result is returned as a Cursor object. ( Bu method çağırılırsa, content provider'a bir select sorgusu atılır, böylece varolan satırlar getirilir, elde edilir. )
insert() : This method inserts a new record into the content provider. ( Bu method çağırılırsa, content provider'a yeni bir record(row, satır) eklenir. )
delete() : This method deletes an existing record from the content provider. ( Bu method çağırılırsa, content provider'dan varolan bir record(row, satır) silinir. )
update() : This method updates an existing record from the content provider. ( Bu method çağırılırsa, content provider'daki varolan bir record(row, satır) güncellenir. )
getType() : This method returns the MIME type of the data at the given URI. (?)

Accessing a provider https://developer.android.com/guide/topics/providers/content-provider-basics.html#ContractClasses
        An application accesses the data from a content provider with a ContentResolver client object. ( Bir uygulama bir ContentProvider'ı contentResolver(diğer bir deyişle contentprovider'ın client'ı) aracılığıyla çağırır. ContentResolver, ContentProvider'ın client'ıdır. )
        This object has methods that call identically-named methods in the provider object, an instance of one of the concrete subclasses of ContentProvider. ( ContentResolverve ContentProvider class'larında aynı isimli method'lar vardır. Örneğin hem ContentResolver hem de ContentProvider class'ında query() method'u vardır. ContentResolver object'in bir method'unu örneğin query() method'unu çağırdığımızda, kendi tanımladığımız ContentProvider object'in aynı isimli method'u, ki bu örnekte query() method'u otomatik olarak çağırılmış olur. )
        The ContentResolver methods provide the basic "CRUD" (create, retrieve, update, and delete) functions of persistent storage. (ContentResolver class'ının method'larını çağırıp, content provider'daki aynı isimli method'u otomatik olarak invoke etmiş oluruz, invoke edilen content provider method'u aracılığıyla da veritabanına satır ekleyebilir, silebilir, güncelleyebilir, table yaratabilir, varolan table'lara select sorgusu atabiliriz. )
        The ContentResolver object in the client application's process and the ContentProvider object in the application that owns the provider automatically handle inter-process communication.( Client uygulamanın process'inde çalışan Content Resolver object ve provider'a sahip olan uygulamanın process'inde çalışan ContentProvider object birbirleriyle haberleşirler, iletişim halindedirler. Yani telefonumuzda 2 uygulama var diyelim, whatsapp ve rehber. whatsapp, rehber uygulamasının verilerine erişebiliyor olsun. dolayısıyla telefonumuzdaki whatsapp uygulamasında contentresolver object'in query() method'unu çağırırsak, rehber uygulamamızdaki contentprovider class'ının query() method'u invoke edilir otomatik olarak. 2 uygulama birbirleriyle haberleşirler. )
        ContentProvider also acts as an abstraction layer between its repository of data and the external appearance of data as tables.( Yukarıdaki örneği düşünürsek, rehber uygulamamızın veritabanına diğer uygulamalar doğrudan erişemezler. Diğer uygulamalar önce kendi içlerinde bir contentresolver object yaratırlar. Sonra bu object aracılığıyla rehber uygulamasındaki contentprovider class'ının method'ları invoke edilmiş olur. Yani diğer uygulamalar contentresolver object'i kullanarak, bir uygulamanın contentprovider'ını tetikler, content provider da bu uygulamanın veritabanına erişir. Yani content provider bir abstraction layer yani soyut bir kavram gibi düşünülebilir, uygulamanın veritabanına doğrudan erişilemez, ancak contentprovider'a istek gönderilir, contentprovider uygulamanın veritabına erişip gelen isteğe cevap verir. contentprovider kendisine iletilen talebi veritabanına gidip söyleyen bir ulaktır. )
        Note: To access a provider, your application usually has to request specific permissions in its manifest file. ( Bir provider'a erişmek isteyen uygulamanın manifest dosyasında gerekli izinler belirtilmelidir. )

        For example, to get a list of the words and their locales from the UserDictionary Provider, you call ContentResolver.query(). (Örneğin UserDictionary isimli bir provider'a erişip ilgili uygulamanın veritabanına bir select sorgusu atmak istiyoruz diyelim. Bunun için contentresolver object'in query method'unu çağırmalıyız. contentResolver object'i elde etmek için getContentResolver() method'u çağırılır.  )
        The query() method calls the ContentProvider.query() method defined by the User Dictionary Provider. The following lines of code show a ContentResolver.query() call: (contentresolver object'inin query() method'unu çağırdığımızda, query() method'una verdiğimiz argument'de belirttiğimiz contentProvider'ın yani UserDictionary content provider'ın query() method'u çağırılır. Aşağıda content resolver object elde edilip bu object'in query() method'u çağırılmıştır, bu method cursor object return eder. )

Explanation 2 :

http://androidexample.com/Content_Provider_Basic/index.php?view=article_discription&aid=120
        ContentProvider used to get data from central repository. Android application contains content provider to provide data to other applications.( Bir Android uygulaması content provider içeriyorsa, uygulamanın veritabanındaki verileri bu content provider'ı kullanarak diğer uygulamalara sağlar, erişilebilir kılar. Yani uygulamanın verilerini tutan veritabanına, dosyaya veya uzaktaki veritabanına erişimi sağlar content provider.)

        You can also create your custom content provider to get data from database / sdcard / media etc.
( Custom bir content provider tanımlayabiliriz, diğer bir deyişle kendi content provider'ımızı tanımlayabiliriz. Bu content provider, uygulamamızın verilerini diğer uygulamalara sağlayacaktır.)

        Content providers create an abstraction layer between its repository of data and external application that are using data.
( Uygulamamızın verilerinin tutulduğu repository olan veritabanı veya dosya veya uzaktaki veritabanı ile uygulamıza erişmek isteyen diğer uygulamalar arasında bir layer'dır content provider. )

        External Application can call Content Provider methods with the use of ContentResolver.
( External uygulamalar, yani diğer uygulamalar contentResolver class'ını kullanarak, bir uygulamanın content provider method'larını çağırırlar. contentResolver ve contentProvider class'larındaki method'ların isimlerinin aynı olduğunu hatırlayalım, yani contentResolver class'ının query method'unu çağırırsak, ilgili uygulamanın contentProvider class'ının query method'u çağırılmış olur. )

        ContentResolver work as ContentProvider client object, with the use of Content Resolver object we can get data from Content Provider.
(ContentResolver class'ını, ContentProvider object'in client'ı gibi düşünebiliriz. contentresolver object contentprovider object'i çağırır yani. )

        ContentProvider and ContentResolver (provider clients) used together to create a interface for data to handles inter-process communication and access data in secure way.
( Bir uygulamanın verilerine doğrudan erişmek güvenli olmadığı için contentProvider kullanarak bir uygulamanın verilerine erişmek yöntemi geliştirilmiştir bu güvenli bir yöntemdir. ContentProvider ve ContentProvider client kullanılarak iki uygulama birbiriyle haberleşebilir, diğer bir deyişle processler arasında iletişim kurulur. )

        Content provider show data to content resolver as one or many tables that will show same as relational database.(?)

        Android provide number of content providers that store common data such as contact informations, calendar information, and media files etc.
(?)


        Content Provider is the mechanism which is used to expose many of a device's data resources for retrieval and update: Contacts, media store, bookmarks, phone-call log, and so on. It’s hard to find an interesting Android app that doesn’t either use or implement (or both) a Content Provider.
( ContentProvider mekanizması sayesinde, cihazımızın uygulamalarındaki birçok veriyi kullanabiliriz, örneğin rehber ve galeri uygulamasındaki verilere erişebilir bu verileri değiştirebiliriz. Bookmark'lara, geçmiş arama kayıtlarına erişebilir bu verileri değiştirebiliriz. Android'deki tüm kaliteli uygulamalar content provider kullanırlar veya kendi content provider'larını implement ederler, veya her ikisini de yaparlar. )
        You address Content Providers by Url, query them with SQL, and iterate them with a Cursor. But there’s a common anti-pattern, a way to misuse them that can potentially get your app into trouble, and maybe we’ve made it a little too easy. ( ContentProvider'a bir url ile erişiriz, SQL ile sorgularız, elde ettiğimiz cursor object'in üzerinde iterate ederek veriler üzerinde gezeriz.  )
        The Content Providers that the Android framework gives you are described in the SDK’s android.provider package summary. For many of them, the framework provides helper classes of one sort or another, to help automate common chores and provide symbolic names for useful constants. ( Android framework'ün bize default olarak sağladığı content provider'lar nelerdir? android.provider isimli package altındadır bunlar, listesi şuradadır : https://developer.android.com/reference/android/provider/package-summary.html    . Bize sağlanan bu content provider'lar için, android framework bize helper class'lar sağlamıştır. )
        The problem is, there are more Content Providers in the system than are documented in that package, and while you can use them, you probably shouldn’t. They’re there because some of the Google-provided apps use them internally to access their own data resources. Because Android is an open-source project, it’s easy enough to find them just by running shell commands like find and grep over the source tree.( Problem şudur, android.provider package'ından dökümante edilenden daha fazla content provider vardır aslında. Dökümante edilmeyen content provider'ları da kullanabiliriz ancak kullanmasak daha iyi olur çünkü bunlar default olarak gelen bazı uygulamalar tarafından kullanılırlar. Android open-source bir project olduğu için, find ve grep gibi shell komutlarını source tree üzerinde çalıştırarak dökümanda olan veya olmayan tüm content provider'ları görebiliriz. )
        (By the way, searching the source tree like this is an excellent idea, something that probably every serious developer does regularly. Not 100% sure of the best way to write code to display a record from the Contacts database? Go ahead, have a look at how the built-in app does it; even better, steal some code; it’s perfectly legal.) ( Search tree üzerinde bu şekilde arama yapmak müthiş bir fikirdir ve tüm tecrübeli android yazılım geliştiriciler bunu sıklıkla yaparlar. Örneğin, Contacts veritabanındaki verilere erişmek için Contacts contentProvider'ı kullanmak istiyoruz, bu sayede rehber uygulamasındaki bir kişinin kaydına erişip bunu göstermek istiyoruz diyelim. Ancak bunu nasıl yapacağımızdan emin olmadığımızı varsayalım. Bu durumda built-in uygulamaların bunu nasıl implement ettiklerine göz atabiliriz ve bunu kendi kodumuzda kullanabiliriz bu son derece yasaldır.  )
        Back to Content Providers. For example, there’s one inside the built-in Messaging (A.K.A. texting or SMS) app that it uses to display and search your history. Just because it’s there doesn’t mean you should use it. The Android team isn’t promising that it’ll be the same in the next release or even that it’ll be there in the next release.( Örneğin built-in SMS uygulamasından rehber uygulamasının verilerine erişebiliriz. Built-in sms uygulamasının kodlarını source tree'den bulup uygun kodu alıp kendi kodumuzda kullanabiliriz. )

        It’s worse than that; someone could ship an Android device based on the current SDK that follows all the rules but has its own enhanced messaging application that doesn’t happen to have such a Content Provider. Your app will break on such a device and it’ll be your fault.( Ancak şöyle bir kötü senaryo da mümkündür. Son çıkan SDK'de rehber uygulamasının content provider'ı değişti diyelim, bu durumda bizim uygulamamız da patlar çünkü uygulamamızda kullandığımız content provider güncel SDK'e sahip bir telefonda var olmadığı için bu telefonda bizim uygulamamız patlar.  )

        So, go ahead and look at the undocumented Content Providers; the code is full of good ideas to learn from. But don’t use them. And if you do, when bad things happen you’re pretty well on your own. ( Hadi dökümante edilmeyen contentProvider'lara gidip bir göz atın. Bu kodlardan çok iyi fikirler öğrenebiliriz, ancak bu fikirlerin hepsini uygulamamalıyız bunun sonuçları kötü olursa bu bizim sorumluluğumuz olur. )

Diğer uygulamalar contentResolver'ın aşağıda gösterilen method'larından birini çağırırlarsa, ilgili uygulamanın aynı isimli method'u otomatik olarak invoke edilmiş olur. Content Provider, bir uygulamanın veri repository'si ve diğer uygulamalar arasında bir layer'dır gibi düşünülebilir.

ContentResolver ( Client object to Access ContentProvider )

        An application gets data from a content provider with a ContentResolver client object.( Bir uygulama content resolver object'i kullanarak bir content provider'dan veri çeker.)
        The ContentResolver provides the basic create, retrieve, update, and delete functions.
( ContentResolver object,CRUD işlemlerini gerçekleştiren fonksiyonlara sahiptir. )
        When application calls ContentResolver method then ContentProvider's identically-named method is called in.
( Diğer uygulamalardan biri content resolver object'in bir method'Unu çağırdığında, bizim uygulamamızın content provider object'inin aynı isimli method'u çağırılır. )

  Example :
 
    If you call getContentResolver().query() method then ContentProvider's query() method called.
 ( Content resolver object'in query() method'unu çağırırsak, ilgili content provider object'in query() method'u çağırılır.  )
    If you call getContentResolver().insert() method then ContentProvider's insert() method called.
 ( Content resolver object'in insert() method'unu çağırırsak, ilgili content provider object'in insert () method'u çağırılır.  )
        Aşağıdaki kodu inceleyelim. Bu kod activity subclass'ı içerisinde çağırılır, tanımladığımız contentProvider subclass'ı içerisinde değil!!! Dikkat et sakın karıştırma !! getContentResolver().query(...)  activity subclass'ında çağırılır, örneğin bir butona basıldığında x method'u çağırılacaksa bu x method'u activity subclass'ında tanımlanacaktır, bu x method'unun içerisinde getContentResolver().query(...)  diyerek, contentResolver object'in query() method'u çağırılır, böylece db manipulation işlemleri yapılır.
// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
            CONTENT_URI,                        // The content URI to access table
            mProjection,                           // The columns to return for each row
            mSelectionClause                    // Selection criteria
            mSelectionArgs,                     // Selection criteria
            mSortOrder);                        // The sort order for the returned rows
    SQL query yazmak konusunda usta olan Android yazılımcılar genellikle contentresolver object'İn query() method'una argument olarak sql query yazılır.
ContentResolver.query() vs. Activity.managedQuery :

        managedQuery() will use ContentResolver's query(). The difference is that with managedQuery() the activity will keep a reference to your Cursor and close it whenever needed (in onDestroy() for instance.) If you do query() yourself, you will have to manage the Cursor as a sensitive resource. If you forget, for instance, to close() it in onDestroy(), you will leak underlying resources (logcat will warn you about it.) ( getContentResolver().query() method'Unu çağırmak yerine managedQuery() method'unu da çağırabiliriz. Peki getContentResolver().query() mi çağırmalıyız managedQuery() mi çağırmalıyız? Aslında managedQuery(), ContentResolver object'in query() method'unu çağırır. Aralarındaki fark ise şudur :
- getContentResolver.query() method'unu kullanırsak, elde ettiğimiz Cursor object'i daha titiz ve hassas kullanmalıyız, Cursor object'ini activity'mizin onDestroy() method'unda kapatmayı unutmamalıyız aksi takdirde memory leak olur.
- managedQuery() method'unu kullanırsak, activity'miz Cursor'ı yönetme işini kendisi üstlenecektir, bizim Cursor object'i manual olarak kapatmamıza gerek kalmayacaktır, cursor object otomatik olarak kapatılacaktır doğru zamanda.  )
        To query a content provider, you can use either the  ContentResolver.query() method or the Activity.managedQuery() method. Both methods take the same set of arguments, and both return a Cursor object. However, managedQuery() causes the activity to manage the life cycle of the Cursor. A managed Cursor handles all of the niceties, such as unloading itself when the activity pauses, and requerying itself when the activity restarts. You can ask an Activity to begin managing an unmanaged Cursor object for you by calling  Activity. startManagingCursor(). ( Content provider'ı sorgulamak için ContentResolver.query()  method'unu da kullanabiliriz, Activity.managedQuery() method'unu da kullanabiliriz. Her 2 method da aynı parametreleri alır, ve her ikisi de bir Cursor object return eder.managedQuery() method'unu kullanırsak, cursor object'in açılması kapatılması vs. gibi tüm yönetim işleri otomatik olarak yapılacaktır bizim manuel olarak cursor object'i manipule etmemize gerek kalmayacaktır.  )
Update:
managedQuery is now deprecated (as of Android 3.0).


getContentResolver().insert(URI, contentValues)
vs
getContentResolver().notifyChange(URI, null)

         In an Activity we can call getContentResolver().insert(URI, contentValues); via a button click.
         In our implementation of ContentProvider at the end of the insert() method, we call getContentResolver().notifyChange(URI, null);

( Yani şunu karıştırma, getContentResolver().insert() veya getContentResolver().query() kodunu activity class'ında çağırırız, örneğin bir butona basıldığında çağırılmasını istediğimiz method'un içerisinde çağırabiliriz getContentResolver().insert() ve  getContentResolver().query() method'larını.
Ancak tanımladığımız contentProvider class'ında ise getContentResolver().notifyChange(URI, null); method'unu çağırırız. Bu 2 method'un çağırıldıkları yerleri unutma, Activity class'ında getContentResolver().query() çağırılır. Custom ContentProvider class'ında ise getContentResolver().notifyChange(URI, null); çağırılır.)

Example  :

        Custom bir ContentProvider tanımlayıp bunu kodumuzda kullanmak istiyorsak çok fazla teknik detay bilmemiz gerekir. Şimdi bunları adım adım inceleyelim.

Step 1: ContentProvider class skeleton

public class BirthProvider extends ContentProvider {
        private SQLiteDatabase db;
        DBHelper dbHelper;
        @Override
        public boolean onCreate() {
                Context context = getContext();
                dbHelper = new DBHelper(context);
                db = dbHelper.getWritableDatabase();

                 if(db == null)
                        return false;
                else
                        return true;       
        }
          // DBHelper class creates and manages the provider's database.
         // Provider'ın veritabanını yönetmek için DBHelper class'ını tanımlarız.
        private static class DBHelper extends SQLiteOpenHelper
        {
                public DBHelper(Context context) {
                        super(context, DATABASE_NAME, null, DATABASE_VERSION);
                }
                ...
        }
}
        ContentProvider'ı extend eden BirthProvider isimli bir class tanımladık. ContentProvider'ın subclass'ında, SQliteOpenHelper class'ını extend eden DBHelper isimli bir static inner class tanımladık. Ayrıca ContentProvider subclass'ının scope'unda, SQLiteOpenHelper object'tine refer eden dbHelper isimli bir reference variable tanımladık.
        ContentProvider subclass'ının onCreate() method'unda, SQLiteOpenHelper subclass'ından bir object yaratırız : dbHelper = new DBHelper(context); DBHelper contructor'ına argument olarak context object verdiğimize dikkat et. DBHelper class'ının context object alan constructor'ında super(context, DATABASE_NAME, null, DATABASE_VERSION); diyerek superclass'ın yani SQLiteOpenHelper class'ın constructor'ı çağırılır.
        ContentProvider'ın subclass'ının onCreate() method'unda, SQLiteOpenHelper subclass'ının object'inin, yani dbHelper'ın refer ettiği object'in getWritableDatabase() method'u çağırılır, getWritableDatabase() method'u SQLiteDatabase object return eder. ContentProvider subclass'ının scope'unda, SQLiteDatabase object'tine refer eden db isimli bir reference variable tanımladık. Veritabanına başarılı bir şekilde erişim sağladıysak, onCreate() method'u true retuırn eder, aksi takdirde false return eder. SQLiteOpenHelper class'ının getWritableDatabase() method'unu burada çağırmak zorunda değiliz. İstersek bunu provider'ın diğer method'ları içerisinde de çağırabiliriz, ancak onCreate() method'unda çağırmak daha mantıklıdır bence.

Step 2: ContentProvider class and SQLiteOpenHelper

public class BirthProvider extends ContentProvider
{
        private SQLiteDatabase db;
        DBHelper dbHelper;
        static final String DATABASE_NAME = "BirthdayReminder";
        static final int DATABASE_VERSION = 1;

        @Override
        public boolean onCreate() {
                Context context = getContext();
                dbHelper = new DBHelper(context);
                db = dbHelper.getWritableDatabase();

            if(db == null)         return false;
            else                      return true;       
        }
       
        private static class DBHelper extends SQLiteOpenHelper
        {
                public DBHelper(Context context) {
                     super(context, DATABASE_NAME, null, DATABASE_VERSION);
                }
        }
}
        ContentProvider class'ımızın scope'unda, kullanacağımız veritabanı ismini ve versiyonunu da tanımlarız.  ContentProvider class'ının içinde tanımlanan SQLiteOpenHelper inner class'ının constructor'ında, superclass'ın constructor'ını invoke ederken ContentProvider scope'unda tanımlanan veritabanı ismi ve veritabanı versiyonunu da contructor'a argument olarak veririz.
       

Step 3 : SQLiteQueryBuilder

        ContentProvider class'ımızın query() method'unu inceleyelim. query() method'u Cursor object return eder. query() method'unda önce bir SqliteQueryBuilder object yaratırız. SqliteQueryBuilder class'ını kullanarak complex query'ler yazabiliriz. Özellikle birden fazla table içeren bir query yazmak için(mesela 2 table'ı join etmek için) SqliteQueryBuilder class'ı kullanmak işimizi kolaylaştırır.
        Aşağıdaki örnekte, tbl_books ve tbl_authors table'larını join ederek kitapların ve yazarların bir listesini oluşturmak için SqliteQueryBuilder class'ını kullanacağız :
        2 farklı table içeren bir sql query'si yazmak istediğimizi düşünelim, örneğin select * from tbl_books, tbl_authors yazmak istiyoruz diyelim. Bunun için SqliteQueryBuilder object'in setTables method'unu çağırırız, bu method'a argument olarak table isimlerini veririz :  queryBuilder.setTables(“tbl_books, tbl_authors”);
        Birden fazla table kullanılacağı zaman, bir sütuna tableismi.sütunismi diyerek refer ederiz. Örneğin tbl_books isimli table'ın title isimli sütununa erişmek için tbl_books.title deriz. Yazmak istediğimiz bir query'nin where clause'unu belirlemek için, SqliteQueryBuilder object'in appendWhere method'unu çağırırız : queryBuilder.appendWhere(“tbl_books.authorid tbl_authors.id”);
Yukarıdaki setTables() ve appendWhere() method'larını çağırınca, şöyle bir query'ye sahip oluruz : select * from tbl_books, tbl_authors where tbl_books.authorid  tbl_authors.id
        Peki select ? from ... where ... sql clause'undaki ? soru işaretli kısmı nasıl ayarlarız. Bunun için bir string array tanımlarız. Hangi table'larıın hangi sütunlarını listede göstermek istiyorsak, yani ? işaretli kısma hangi table'ın hangi sütununu yazmak istiyorsak, tanımladığımız string array'e eleman olarak koyarız.
String asColumnsToReturn[] { “tbl_books.title”, “tbl_books.id”, “tbl_authors.firstname”, “tbl_authors.lastname”, “tbl_books.authorid” }; SqliteQueryBuilder object'in query() method'unun 2. parametresine bu array'i veririz :
Cursor c = queryBuilder.query(mDatabase, asColumnsToReturn,
null, null, null, null,strSortOrder);
       
        Peki select .. from ... where ... order by ?  sql statement'daki ? soru işaretli kısmı nasıl belirleriz. SqliteQueryBuilder object'in query() method'unun sonuncu parametresine vereceğimiz string ? yerine koyulur :
String strSortOrder = "title ASC";
Cursor c queryBuilder.query(mDatabase, asColumnsToReturn, null, null, null, null,strSortOrder);
TÜm bunlardan sonra şöyle bir sql query elde etmiş oluruz :
SELECT tbl_books.title, tbl_books.id, tbl_authors.firstname, tbl_authors.lastname, tbl_books.authorid
FROM tbl_books
INNER JOIN tbl_authors on tbl_books.authorid = tbl_authors.id
ORDER BY title ASC;

        Peki select .. from tbl_books AS A, tbl_authors AS B where ... order by ..  sql statement'daki mavi kısmı nasıl belirleriz. Yani istediğimiz table'ların istediğimiz sütunlarını nasıl isimlendiririz? SqliteQueryBuilder object'in setProjectionMap() method'unu çağırarak. setProjectionMap() method'una argument olarak bir HashMap object verilir:
 ContentProvider class'ı içerisinde :
private final static HashMap<String, String> BirthMap; 
static {
        BirthMap = new HashMap<String, String>();
        sDictProjectionMap.put(MAIN_COLUMN_ID, MAIN_COLUMN_ID_NEW);
        sDictProjectionMap.put(MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD1_NEW);
        sDictProjectionMap.put(MAIN_COLUMN_WORD2, MAIN_COLUMN_WORD2);
        sDictProjectionMap.put(MAIN_COLUMN_LOCALE, MAIN_COLUMN_LOCALE);
}
queryBuilder.setProjectionMap(BirthMap);
BirthMap'e eleman ekleme işini static scope'da yaptığımıza dikkat et. Burası önemlidir.
queryBuilder.query() method'unda yapmamız gereken bir şey yoktur.
        Örneğin, books isimli table'daki pagenumber sütununun ismini A yapmak istiyorum diyelim.     Bunu şu sql komutunu çalıştırarak yapabiliriz : SELECT books.pagenumber AS A
        Bunu SqliteQueryBuilder class'ını kullanarak nasıl yapabiliriz peki? ContentProvider class'ı içerisinde static bir HashMap<String, String> object tanımlarız. Sonra HashMap object'in put() method'unu çağırarak HashMap collection'a eleman koyarız. put(), method'unun aldığı ilk argument yeni sütun ismi olacaktır, 2.argument ise current sütun ismidir.
HashMap<String, String> gProjectionMap= new HashMap<String, String>();
gProjectionMap.add("A"," books.pagenumber")
SqliteQueryBuilder  qb = new SqliteQueryBuilder ();
qb.setTables("apple");
qb.setProjection(gProjectionMap);
// The result:
s == "SELECT apple.orange AS brumble FROM apple WHERE color = ?";
        For example you could map "name" to "people.name". Yani people table'ındaki name sütununa refer etmek için name diyebiliriz sadece. Şu gibi : select name as people.name from some_table. Here, name maps to people.name .
        If a projection map is set it must contain all column names the user may request, even if the key and value are the same. ( Eğer setProjectionMap() method'u kullanılarak bir projection map set edilecekse, bu projection map kullanıcının istek gönderebileceği tüm sütun isimlerini içermek zorundadır. Key ve value aynı olsa bile! Çoğu örnekte, çoğu satır için key ve value'nün aynı olması işte bu yüzdendir. )
        setProjectionMap() method'una çok sık ihtiyacım olacağını sanmıyorum ancak neredeyse tüm contentprovider class'larında bu method kullanılmış. Hatta sütunları yeniden isimlendirmeseler bile bunu kullanmışlar. Yukarıda da aynı sütunlara aynı isimleri verdik, bunu yazmak da yazmamak da aynı sonucu verir.
        Alternatif olarak, SQLiteQqueryBuilder object'in setProjectionMap() method'unu kullanmak yerine, select ? from .. sql statement'daki ? soru işaretli yere koymak istediğimiz şeyi queryBuilder.query() method'unun 2.parametresine veririz. Örnek :
projection = new String[]
{
TABLE_NAME + "._id" AS " + TABLE_NAME + "_id",
TABLE_NAME + "." + "name" + " AS " + TABLE_NAME + "_" + "name"
};
Cursor cursor = queryBuilder.query( .. , projection, .. , .. , .. , .. , .. );
        SQLiteQueryBuilder class'ını tanımlayı bitirdik. artık 3.adımı incelemeye kaldığımız yerden devam edebiliriz :
                     switch block'u inceleyelim. switch block'da, UriMatcher class'ının static bir method'u olan match() method'u çağırılır, match method'unua argument olarak query method'unun aldığı argument olan URI object verilir. Buna göre bir işlem yapılır. Ayrıntıları bir sonraki adımda inceleyeceğiz.

Step 4 : UriMatcher

                     ContentProvider class'ında bir URI object yaratılır. Bu URI object'in type'ını yani list mi item mı type'ında olduğunu bulmak için UriMatcher class'ı kullanılır. URI  object'in nasıl yaratıldığına dikkat edelim. Önce content url'i string olarak yazarız, sonra bu string Uri class'ının static method'u olan parse() method'una verilir, bu method URI object return eder.
static final String PROVIDER_NAME = "com.javacodegeeks.provider.BirthdayProv";
static final String URL = "content://" + PROVIDER_NAME + "/friends";
static final Uri CONTENT_URI = Uri.parse(URL);

                     The UriMatcher is used to determine the URI type: list or item. ContentProvider class'ında yarattığımız URI object'in type'ını yani liste mi item mı type'ında olduğunu bulmak için UriMatcher class'ı kullanılır. ContentProvider class'ımızda bir UriMatcher object yaratırız.  UriMatcher object'e refer eden variable static olduğu için ya declare edilirken set edilmek zorundadır, ya da declare edildikten sonra static bir scope'da declare edilmek zorundadır.
uriMatcher.addURI(PROVIDER_NAME, "friends", FRIENDS)    : Defines the list type. Any URI that uses the com.javacodegeeks.provider.BirthdayProv authority and has a path named "friends" returns the value FRIENDS.
uriMatcher.addURI(PROVIDER_NAME, "friends/#", FRIENDS_ID) : Defines the item type. Any URI that uses the com.javacodegeeks.provider.BirthdayProv authority and has a path that looks like friends/# (where # is a number) returns the value FRIENDS_ID.

// integer values used in content URI
static final int FRIENDS = 1;
static final int FRIENDS_ID = 2;

// maps content URI "patterns" to the integer values that were set above
static final UriMatcher uriMatcher;
static
{
                     uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
                     uriMatcher.addURI(PROVIDER_NAME, "friends", FRIENDS);
                     uriMatcher.addURI(PROVIDER_NAME, "friends/#", FRIENDS_ID);
}
addURI method'unun aldığı 1.parametre authority'dir,2. parametre path'dir. Bu authority ve path'e sahip olan uri object FRIENDS_ID'ye yani 2'ye map eder.

        Using a content provider involves calling its data operations with REST-style URIs defined by the UriMatcher class. UriMatcher provides a simple string matching utility for REST-based URLs, with support for wild-carding strings. ( ContentProvider kullanmak, REST-style URI object'ler ile veri işlemleri gerçekleştirmeyi içerir. UriMatcher class'ı, REST tabanlı URL'ler için bir string matching(karşılaştırma) yapmayı sağlar. )
        Content provider URLs always take the following form:
content://authority/path/id where authority is the Java package of the content provider namespace (often the Java namespace of the content provider implementation). Here are some example content provider URIs: (Content provider URL'leri, genellikle şu formdadır : content://authority/path/id . Buradaki authority kısmı genellikle content provider class'ının full path'idir, bu zorunlu değildir ancak convention yani gelenek böyledir. Bazı örnek content provider URI'lar şöyledir.  )
// references a person
content://contacts/people/25
// this URI designates the phone numbers of the person whose ID is "25"
content://contacts/people/25/phones

Content URIs can represent either of two forms:
content://content_provider'in_path'i/items -> This URI represents a request for all values of that type (e.g., all items). (select * from items gibi düşün. )
content://content_provider'in_path'i/items/5  -> Appending a trailing /<rownumber>, as shown here, represents a request for a single record (e.g., “the fifth item”). (select * from items where id=5 gibi düşün.)

        It’s good form to support access to your provider using both these forms. ( Tanımladığınız ContentProvider class'ınızda iki farklı content provider type'ını(list ve item) da düşünerek kod yazmanız tavsiye edilir.  )

Örnek : ContentProvider class tanımlayalım. Content Provider URI form'unu string olarak yazarız. A URI ending in ‘items’ will correspond to a request for all items, and ‘items/[rowID] represents a single row. (Sonu "items" ile biten bir URI tüm item'lar için bir request'dir. Sonu "items/rowID" ile biten bir URI specific bir item için bir request'dir.  ) :
private static final String myURI = “content://com.paad.provider.myapp/items”;
                     Uri.parse() method'Unu çağırıp Uri object'i elde ederiz:
public static final Uri CONTENT_URI = Uri.parse(myURI);
                     Tüm satırlar için yapılacak request'e karşılık gelecek olan ve specific bir satıra yapılacak bir request'e karşılık gelecek olan 2 tane int variable tanımlarız, bu variable'lar private static final olmalıdır:
private static final int ALLROWS = 1;
        private static final int SINGLE_ROW = 2;
                     UriMatcher type'ında bir reference variable tanımlarız. Sonra UriMatcher object yaratırız. Sonra UriMatcher object'in addURI() method'unu çağırarak UriMatcher object'e elemanlar ekleriz, böylece com.paad.provider.myApp/items uri'a request gelirse bunun 1'e map etmesini, com.paad.provider.myApp/items/# uri'a request gelirse 2'ye map etmesini sağlarız.

public class MyProvider extends ContentProvider
{
        private static final String myURI = "content://com.paad.provider.myapp/items";
        public static final Uri CONTENT_URI = Uri.parse(myURI);

        private static final int ALLROWS = 1;
        private static final int SINGLE_ROW = 2;
        private static final UriMatcher uriMatcher;
        // Populate the UriMatcher object, where a URI ending in ‘items’ will
        // correspond to a request for all items, and ‘items/[rowID]’
        // represents a single row.
        static {
               uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
               uriMatcher.addURI(“com.paad.provider.myApp”, “items”, ALLROWS);
               uriMatcher.addURI("com.paad.provider.myApp", "items/#", SINGLE_ROW);
        }
}
        You can use the same technique to expose alternative URIs for different subsets of data, or different tables within your database from within the same Content Provider. ( Aynı ContentProvider'ın içerisindeki diğer table'ları da uriMatcher object'e yukarıdaki şekilde ekleyebiliriz. Farklı URI'lar kullanarak farklı veri altkümelerini (mesela id'si 25 olan kişinin/kişilerini phone sütunlarını) de yukarıda şekilde urimatcher object'e ekleyebiliriz. )
- Bu URIMatcher object'i, ContentProvider class'ının query() method'unda şu şekilde kullanabiliriz:
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
{
        ...
        switch (uriMatcher.match(uri)) {
        // maps all database column names
        case ALLROWS:
                ...
        case SINGLE_ROW:
                ...
        default:
                throw new IllegalArgumentException("Unknown URI " + uri);
         }
         ....
}

        It’s also good practice to expose the names and indexes of the columns available in your provider to simplify extracting information from Cursor results.

Example :
public static final String AUTHORITY = "com.androidbook.TrackPointProvider"
private static final int TRACKPOINTS = 1;
private static final int TRACKPOINT_ID = 10;
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
      sURIMatcher.addURI(AUTHORITY, “points”, TRACKPOINTS);
      sURIMatcher.addURI(AUTHORITY, “points/#”, TRACKPOINT_ID);
}

- First, arbitrary numeric values are defined to identify each different pattern:
private static final int TRACKPOINTS = 1;
private static final int TRACKPOINT_ID = 10;
-  Next, a static UriMatcher instance is created for use:
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- Next, the URI values are added to the matcher with their corresponding identifiers:
static {
      sURIMatcher.addURI(AUTHORITY, “points”, TRACKPOINTS);
      sURIMatcher.addURI(AUTHORITY, “points/#”, TRACKPOINT_ID);
}
- Bu URIMatcher object'i, ContentProvider class'ının query() method'unda şu şekilde kullanabiliriz:
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
{
        ...
        switch (uriMatcher.match(uri)) {
        // maps all database column names
        case TRACKPOINTS:
                ...
        case TRACKPOINTS_ID:
                ...
        default:
                throw new IllegalArgumentException("Unknown URI " + uri);
         }
         ....
}

Step 5 : Cursor.setNotificationUri(),Cursor.getNotificationUri()

        İncelediğimiz BirthProvider.java class'ının query() method'undaki her şeyi inceledik öğrendik, sadece cursor.setNotificationUri() method'u kaldı öğrenmediğimiz. Şimdi de bu method'u öğrenelim hadi!!!!!
Cursor class'ı, şu 2 method'u içerir:
setNotificationUri (ContentResolver cr,  Uri uri)
        Register to watch a content URI for changes. This can be the URI of a specific data row (for example, "content://my_provider_type/23"), or a a generic URI for a content type.
Uri  getNotificationUri()
Return the URI at which notifications of changes in this Cursor's data will be delivered, as previously set by setNotificationUri(ContentResolver, Uri).

----
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
{
        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
        ...
        Cursor cursor = queryBuilder.query(database, projection, selection, selectionArgs, null, null, sortOrder);
       
        // register to watch a content URI for changes
        cursor.setNotificationUri(getContext().getContentResolver(), uri);
        return cursor;
}


        Inside the contentProvider's insert, update, delete methods, you need to call getContext().getContentResolver().notifyChange(uri, null); to notify change to the uri observers. ( contentProvider class'ının insert(), update() delete() method'larında şu contentResolver object'in notifyChange method'u çağırılır :
getContext().getContentResolver().notifyChange(uri, null); 
insert(),update(),delete() method'ları çağırıldığında veritabanında bir değişiklik olur, ve getContext().getContentResolver.notifyChange(uri,null) method'u şu amaçla çağırılır: notifyChange method'unun aldığı 1. argument olan uri object'i observe eden kişilere bir değişiklik olduğu haberi verilir. )
So if you don't call cursor#setNotificationUri(), your CursorLoader will not receive notification if data underlying that uri changes. ( contentProvider class'ının query() method'unda SQLiteQueryBuilder object'in query() method'u çağırılarak bir Cursor object elde edilir. Bu cursor object'in setNotificationUri() method'u çağırılarak, uri object'de bir değişikilk olursa bu değişikliğin Cursor object'e de yansıması sağlanır.
        Diyelim ki uygulamayı çalıştırdık. Sonra insert(), update() ve delete() method'larını çağırarak veritabanında değişiklikler yaptık diyelim. insert(), update() ve update() method'larının sonunda çağırdığımız getContext().getContentResolver().notifyChange(uri, null); kodu
query() method'unun sonunda çağırdığımız cursor.setNotificationUri(getContext().getContentResolver(), uri); satır sayesinde bu cursor'ı tetikler. Yani query method'unda elde ettiğimiz Cursor object'in veritabanında yapılan değişikiklerden haberi olur. Sonra query method'unda bu cursor object'i return ederiz.
        Peki query() method'unu sonunda bu kodu çağırmazsak ne olur? cursor.setNotificationUri(getContext().getContentResolver(), uri); veritabanında yapılan değişikiklerden cursor object'in haberi olmaz, dolayısıyla contentprovider class'ının query() method'unu çağırdığımızda veritabanının güncel halini sorgulayamayız.  )
public class BirthProvider extends ContentProvider {
  @Override
  public Cursor query(){
        Cursor cursor = queryBuilder.query(...);
        // register to watch a content URI for changes
        cursor.setNotificationUri(getContext().getContentResolver(), uri);
  }
}
CursorLoader registers observer for the cursor, not to the URI.

        So once that ContentResolver knows that URI's content has been changed [ this happens when you call getContext().getContentResolver().notifyChange(uri, contentObserver); inside ContentProvider's insert(), update() and delete() methods ] it notifies all the observers including CursorLoader's ForceLoadContentObserver.
( ContentProvider'ın insert(), update() ve delete() method'larından biri çağırıldığında, bu method'ların içerisindeki şu kod çalışır : getContext().getContentResolver().notifyChange(uri, contentObserver);  bu kodun çalışmasıyla ContentResolver object, URI'ın içeriğinin değiştiğinden haberdar olur. Bu, URI'ı observe eden herkese bu değişiklik haber verilir. Örneğin query method'unda şu kod olduğu için : cursor.setNotificationUri(getContext().getContentResolver(), uri);   cursor object bu değişiklikten haberdar olur ve güncel veritabanına refer eder. )

ContentProvider class'ındaki query() method'u şöyle bir satır içerir:
// Tell the cursor what uri to watch, so it knows when its source data changes (Cursor object'e hangi uri object'i izleyeceğini söyleriz burada. Bu sayede bu uri'daki içerik değişince cursor object'in bundan haberi olur. )
cursor.setNotificationUri(getContext().getContentResolver(), uri);

CursorLoader get cursor back and registers an observer.( CursorLoader, cursor'ı bir observer'a register eder, yani cursor bu observer'ı dinler. Yani bu observer'da bir değişiklik olursa cursor'ın bundan haberi olacaktır. ) When someone modifies data, ContentProvider notifies ContentResolver about changes: ( Uri içeriğinde(yani veritabanında)bir değişiklik olursa şu method çağırılarak contentProvider'ın contentResolver'a haber vermesi sağlanır: )
getContext().getContentResolver().notifyChange(uri, null);
ContentResolver in its turn notifies all registered observers. Observer, registered by CursorLoader, forces it to load new data.( ContentResolver da bu uri observe eden(dinleyen, bu uri'a register edilmiş olan) observer'lara değişikliği haber verir. Observer güncel veritabanını görür. )

Step 6 : Uri.getLastPathSegment()

uri.getLastPathSegment() nedir?
import android.net.Uri;
Uri uri = Uri.parse("http://example.com/foo/bar/42");
String token = uri.getLastPathSegment();
content uri, şu formdadır : prefix>://<authority>/<data_type>/<id>
bu form'daki son kısmı yani id kısmını nasıl elde edebiliriz? uri object'in getLastPathSegment() method'unu çağırarak. Yukarıdaki uri object için bu method, "42" return eder.

String getLastPathSegment ()
Returns the decoded last segment or null if the path is empty. ( content uri path'inin son segment'ini string olarak return eder. )

String getPath()
Returns the decoded path, or null if this is not a hierarchical URI (like "mailto:nobody@google.com") or the URI is invalid. (content uri path'ini string olarak return eder. Yukarıdaki uri object için bu method, "http://example.com/foo/bar/42" return eder. )

List<String> getPathSegments ()
Gets the decoded path segments.
Returns decoded path segments, each without a leading or trailing '/' ( Bu method, elemanları string olan bir List object return eder. content uri path'ini "/" karakterlerine göre parse eder, elde ettiği parçaları listeye ekle, bu listeyi return eder. Yukarıdaki uri object için bu method, "example.com", "foo", "bar","42" string'lerini içeren bir List object return eder. )

Örnek : Aşağıda, content provider class'ının query() method'u gösterilmiştir. Bu method'da, uri object'in getLastPathSegment'i çağırılarak content url path'inin son kısmı elde edilmiştir, bu kısım id'dir bu örnek için. Bu id, dbHelper.getReadableDatabase().query() method'unun selection argument'ine verilmiştir bu örnekte.

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        if (uri.getLastPathSegment() != null) {
                String id = uri.getLastPathSegment();
                selection = "_id = ?";
                selectionArgs = new String[] {id};
        }
        return dbHelper.getReadableDatabase().query(
                        TABLE_NAME
                        , null // columns
                        , selection
                        , selectionArgs
                        , null // groupBy
                        , null // having
                        , null // orderBy
        );
}
        SQLiteDatabase class'ının query() method'unun aldığı son 3 argument sırasıyla SQL'deki groupBy, having ve orderBy 'a benzer.

Step 7 : TextUtils.isEmpty(str)

Question :  TextUtils.isEmpty(string) ve  string.isEmpty() arasındaki fark nedir? Her ikisi de kullanılarak bir string'in boş olup olmadığı yani şuna "" eşit olup olmadığı check edilir. )
Answer :
        You can use android.text.TextUtils.isEmpty() instead. This method also checks to see if the String is null and 0-length since API level 1.
        TextUtils.isEmpty(string) is always preferred over string.isEmpty() because the later will throw a NullPointerException for any null string whereas the first will always return a boolean value. (string.isEmpty() method'u değil de TextUtils.isEmpty(string) method'unu tercih etmelisiniz. Çünkü bir null string için string.isEmpty() method'unu çağırırsak NullPointerException throw eder.  TextUtils.isEmpty(string) method'u ise exception throw etmez, her zaman için boolean return eder. )

        In code, the first simply calls the equivalent of the other, plus a null check :        return string == null || string.length() == 0;
(TextUtils.isEmpty(string) method'unun implementation'ı aşağıda gösterilmiştir. Aşağıda da gördüğümüz üzere, bu method'un implementation'ı, string.isEmpty() method'unun implementation'ını(string.length==0) da içerir, buna ek olarak string'in null olup olmadığını da check eder. )
public class TextUtils
{
        ...
        public static boolean isEmpty(CharSequence str) {
                if (str == null || str.length() == 0)
                        return true;
                else
                        return false;
        }
}
Örnek : Aşağıdaki örnekte ContentProvider class'ının update() method'unun implementation'ı gösterilmiştir :

Step 8 : getType(Uri) method of ContentProvider

        ContentProvider class'ının getType() method'unu inceleyelim. Bu method ne işe yarar ne için kullanılır?
        getType(Uri) returns the MIME type of data in the content provider. (ContentProvider'daki verilerin MIME type'ını return eder. )
        getType() method uses the UriMatcher on line 19 to determine which MIME type to return. If the URI is a list URI, it returns TASKS_MIME_TYPE. If it’s an item URI, it returns TASK_MIME_TYPE. ( getType() method'unu inceleyelim. Switch block'da, UriMatcher object'in match() method'una argument olarak URI object verilmiştir.
        Eğer URI bir list URI ise, yani FRIENDS case'i için , tüm satırları getirmek için kullanılır. List type'ında bir URI için getType() method'unun return edeceği string "vnd.android.cursor.dir" ile başlamak zorundadır.
        Eğer URI bir item URI ise, yani FRIENDS_ID case'i için, belirli bir satırı getirmek için kullanılır. Item type'ında bir URI için getType() method'unun return edeceği string "vnd.android.cursor.item" ile başlamak zorundadır.  )
@Override
public String getType(Uri uri)
{
        switch (uriMatcher.match(uri)){
            case FRIENDS:               // Get all friend-birthday records
                return "vnd.android.cursor.dir/vnd.example.friends";
            case FRIENDS_ID:  // Get a particular friend
                return "vnd.android.cursor.item/vnd.example.friends";
            default:
                throw new IllegalArgumentException("Unsupported URI: " + uri);
         }
}
        getType(Uri uri) will usually only be called after a call to ContentResolver#getType(Uri uri). It is used by applications to retrieve the MIME type of the given content URL. If your app isn't concerned with the data's MIME type, it's perfectly fine to simply have the method return null.( ContentResolver class'ının getType() method'U çağırıldıktan sonra, ContentProvider class'ının getType() method'u da çağırılır. Uygulamalar, getType() method'unu, bir URL'in MIME type'ını elde etmek için kullanırlar. Eğer uygulamanız, verilerinizin MIME type'ı ile ilişkili değilse, getType() method'unun implementation'da null return etmeniz yeterli olacaktır. )
        ----
https://developer.android.com/reference/android/content/ContentProvider.html
        Implement this to handle requests for the MIME type of the data at the given URI. The returned MIME type should start with vnd.android.cursor.item for a single record, or vnd.android.cursor.dir/ for multiple items. This method can be called from multiple threads, as described in Processes and Threads. ( Diyelim ki ekrandaki bir butona bastık bu butona basınca, bir URL'e istek yapıldı diyelim, bir URI object yaratıldığını düşünelim. contentresolver object'in getType() method'unu çağırdık kodda diyelim. contentresolver object'in getType() method'u çağırıldıktan sonra otomatik olarak contentprovider object'in getType() method'u çağırılır. getType() method'u,  istek yapılan URI object'e göre bir string return eder. uygulamamızın herhangi bir yerinde, contentresolver object'in getType() method'unu çağırarak, URI object'in type'ını string olarak elde edebiliriz. Örneğin bir .jpeg dosyasının url'inden oluşturulmuş bir URI object olsun. Bunun için contentResolver'ın getType() method'unu çağırdığımızda, images/.jpeg gibi bir şey elde ederiz. Buna gerçekten ihtiyacın olduğunda kod içinde deneyerek gör. )
        Note that there are no permissions needed for an application to access this information; if your content provider requires read and/or write permissions, or is not exported, all applications can still call this method regardless of their access permissions. This allows them to retrieve the MIME type for a URI when dispatching intents. ( Tüm uygulamalar, tüm contentProvider'ların getType() method'larına erişebilirler. ContentProvider'ınızın read/write erişimleri olmasa bile, hatta contentProvider'ınızı export etmeseniz bile, erişim yetkileri ne olursa olsun, tüm uygulamalar getType() method'unu çağırabilirler, böylece URI object'in MIME type'ını öğrenebilirler. )
---------
http://stackoverflow.com/questions/5351669/why-use-contentprovider-gettype-to-get-mime-type
        For example, you're writing content provider for picture gallery. You should mention in your getType() method that you provide pictures - jpg or png. So, when one will launch image gallery, it will be able to show built-in pictures and pictures provided by your content provider.( Diyelim ki bir resim galerisi uygulaması için bir contentProvider yazıyoruz. Bu durumda, contentProvider'ımızın, getType() method'unda uygulamada kullanılması muhtemel tüm resim dosyalarının uzantılarını belirtmeliyiz. Bu sayede, uygulamamızı çalıştırdığımızda, bu getType() method'unda tanımladığımız uzantıdaki resim dosyaları gösterilebilir olur. Burayı açıkladım ancak ben de tam anlamadım. )
( Aşağıdaki pseudocode kodda, contentProvider'ın client'ı yani contentProvider'a erişen,kullanan bir kodda, yani contentProvider class'ı haricinde bir yerde getProviders(); method'unu çağırarak tüm system'de tanımlı tüm provider'ları içeren bir List object elde ederiz. jpg uzantılı dosyalar için bir Type object yaratırız. Sonra tüm provider'lar üzerinde tek tek gezip provider'ın getType() method'unu çağırarak provider'ın MIME type'ını elde ederiz ve bu type'ın type object ile aynı olup olmadığına bakarız. Aynı ise bu provider'ı provider'lar listemize ekleriz. Böylece .jpg uzantılı dosyalara refer edebilecek URI'ları destekleyen tüm contentProvider'ların bir listesini elde etmiş oluruz. )
List contentProviders = getProviders();
List resultProviders;
final Type type = Type.JPG;
for (ContentProvider provider : contentProviders) {
  if (type == provider.getType()) {
     resultProviders.add(provider);
  }
}
         So in this case you are getting a list of all providers in the system and filter on the type of data they return, for example to display an image browser with all available JPGs on the device? :) 
Yes, that's right. I'm not sure this is the code you're going to write whenever, but it is good to show the concept.
-----------
Implement the getType() method
        Every content provider must return the content type for its supported URIs. The signature of the method takes a URI and returns a String. The next code sample shows the getType() method of a sample content provider. ( ContentProvider, desteklediği URI'ların content type'larını return etmek zorundadır. getType() method'u argument olarak URI object alır, string return eder. )
@Override
public String getType(Uri uri) {
   switch (URI_MATCHER.match(uri)) {
   case ITEM_LIST:
      return Items.CONTENT_TYPE;
   case ITEM_ID:
      return Items.CONTENT_ITEM_TYPE;
   case PHOTO_ID:
      return Photos.CONTENT_PHOTO_TYPE;
   case PHOTO_LIST:
      return Photos.CONTENT_TYPE;
   case ENTITY_ID:
      return ItemEntities.CONTENT_ENTITY_TYPE;
   case ENTITY_LIST:
      return ItemEntities.CONTENT_TYPE;
   default:
      return null;
   }
}
        As you can see this method is pretty simple. You just have to return the appropriate content type – defined within your contract class – for the URI passed into this method. ( getType() method'unu implement etmek çok kolaydır. Tek yapmamız gereken hangi URL için hangi string'i return edeceğimizi implement etmektir. Muhtemel URL'ler genellikle contract class'da static final olarak tanımlanırlar.
        Daha önce de söylediğimiz gibi, contentProvider'ın client'ı yani contentProvider'a erişen, kullanan bir kodda, yani contentProvider class'ı haricinde bir yerde contentResolver object'in getType() methoD'unu çağırırız, böylece otomatik olarak ilgili contentProvider'ın getType() method'u çağırılır. URI object'in MIME type'ı elde edilir bu şekilde.
        Elde ettiğimiz URI object'in MIME type'ını, contentProvider'ın contract class'ında tanımlı type'lar ile kıyaslayıp buna uygun aksiyon alabiliriz. )

Step 9 : insert() method of ContentProvider

        Bu örnekte, butona tıklayınca, activity class'ında tanımladığımız addBirthday() method'u çağırılır.
        addBirthday() method'u çağırıldığında, EditText'lere yazdığımız string'leri okuruz. Bu string değerlerini sırasıyla  "name" ve "birtday" sütunlarına vereceğiz.
        addBirthday() method'unda, getContentResolver().insert(BirthProvider.CONTENT_URI,value) method'unu çağırarak birthTable isimli table'a value isimli ContentValues object'e eklediğimiz değerleri insert ederiz.

import android.content.ContentUris;

long row = database.insert(TABLE_NAME, "", values);
// If record is added successfully
if(row > 0) {
         Uri newUri = ContentUris.withAppendedId(CONTENT_URI, row);
         getContext().getContentResolver().notifyChange(newUri, null);
         return newUri;
}     
        ContentProvider class'ının insert() method'unda, SQLiteDatabase object'in insert() method'unu çağırırız, bu method aldığı 1.argument'deki table'a satır insert eder. Eğer insertion başarılı bir şekilde gerçekleşirse, insert() method'u 0'dan büyük bir değer return eder. URI object'imize elde ettiğimiz bu değeri append ederiz ContentUris isimli class'ın withAppendedId() method'unu çağırarak.ContentUris.withAppendedId(CONTENT_URI, row) bir Uri object return eder. Sonra getContext().getContentResolver().notifyChange(newUri, null) diyerek, bu uri'da gerçekleşen değişikliği haber veririz. ContentResolver class'ının notifyChange() method'una yeni elde ettiğimiz uri object'i verdiğimize dikkat et. insert() method'u yeni uri object'i return eder. )

Step 10 : ContentProvider , URI object, which table

        ContentProvider class'ında tanımlanan URI object'in authority'si "com.javacodegeeks.provider.BirthdayProv"dir. Bu contentprovider için hangi table'ın kullanılacağı nerede belirlenmiştir peki? Bu sorunun tam cevabını bilmiyorum.
        Contentprovider object yaratılırken, contentProvider class'ının onCreate() method'u çağırıldığında, contentProvider class'ının içerdiği SQLiteOpenHelper class'ından da bir object yaratılır, dolayısıyla birthtable isimli bir table yaratılır.
        MainActivity'de tanımlanan 3 method'a bakalım. 3 butona basınca bu 3 method çağırılır. Her 3 method'da da contentResolver object'in delete,insert,update method'larının 1. argument'leri URI object alır. Hangi table'a insert atacağımızı, hangi table'a sorgu atacağımızı,  hangi table'ı güncelleyeceğimizi, contentProvider'ın insert(), query() ve update() method'larında belirtiriz.
        Peki mademki hangi table'a insert atılacağını insert(), query() ve update() method'larında belirledik. O halde URI object'e neden ihtiyacımız var? Çünkü bu örnekte sadece bir tane table söz konusu. Eğer birden fazla table söz konusuysa, URIMatcher object'in match() method'una URI object'i vererek URI 'a göre uygun table'ı seçebiliriz contentprovider class'ının insert(), query() ve update() method'larında.

Step 11 : Declare provider in AndroidManifest.xml

<provider android:name=".BirthProvider"
 android:authorities="com.javacodegeeks.provider.BirthdayProv">
 </provider>
android:authorities : provider'ın uri'ının authorities'ini beliritiriz.
android:name : provider class'ının path'İni beliritiriz.
---------
        SQL dersimizde contract class'ını anlatmıştık. ContentProvider kullanırken contract class kullanmak istiyorsak SQL derslerimizi incelemeliyiz.
        Contract class kullanılarak content provider implementation yapılması örneği aşağıdaki kodlarda mevcuttur:
http://www.grokkingandroid.com/android-tutorial-writing-your-own-content-provider/
https://bitbucket.org/grokkingandroid/cpsample/downloads  Bu proje bayağı karmaşık sen sadece provider klasörü altındaki dosyalara bak.

2 - https://github.com/udacity/android-content-provider

3 - https://gist.github.com/dustin-graham/9146366   RainEmployeeProvider.java  birden fazla authority table vs var bu örnekte.

Step 12 : ContentUris.withAppendedId(CONTENT_URI, row)


        ContentProvider class'ının sadece insert() method'unda Uri newUri = ContentUris.withAppendedId(CONTENT_URI, row); kullanılıp yeni uri elde edilir. Bu method, yeni uri object'i return eder.
        ContentProvider class'ının update() method'unda uri object'de değişiklik olmaz. SQLiteDatabase object'in update() method'unun return ettiği değeri return eder.
        ContentProvider class'ının delete() method'unda uri object'de değişiklik olmaz. SQLiteDatabase object'in delete() method'unun return ettiği değeri return eder.

Hiç yorum yok:

Yorum Gönder