Smart Mirror Mobile Uygulama
Merhaba arkadaşlar!
Smart Mirror projesi için ihtiyacımız olan diğer bir uygulama akıllı telefonumuz için olacaktır. Akıllı telefona yükleyeceğimiz bu uygulama ile Firebase ortamına etkinlik kaydı yapabilir, var olan kayıtları listeleyebilir veya silebiliriz.
Uygulama arayüzü aşağıdaki gibi olacaktır.
Uygulama Ana sayfasında (sağda) kayıtlı olan etkinliklerin listelenmesi sağlanır. Burada sağ alt köşede bulunan butona tıkladığımız zaman “Registration Form” (solda) yani Kayıt Formu başlatılır. Bu arayüzü kullanarak etkinliklerinizi kayıt edebilirsiniz.
Yukarıda verilen resimde ise kayıt formunda bulunan Time ve Date butonlarına tıklandığı zaman başlatılan saat ve takvimi görebilirsiniz.
Mobile uygulamada geliştireceğimiz bir servis ile zamanı gelen bir etkinlik veya etkinlik grubunu akıllı saatimize göndermeyi sağlayacağız. Akıllı saat uygulamasını daha sonraki yazıda geliştireceğiz.
Mobil uygulama için geliştireceğimiz sınıflar hiyerarşik olarak şu şekilde olacaktır.
MyAdapter –> Firebase ortamında kayıtlı olan verileri RecyclerView içinde listeleyen sınıf.
DatePickerFragment –> Takvim uygulamasını gösteren sınıf.
TimePickerFragment –> Saat uygulamasını gösteren sınıf.
Note –> Firebase ortamında kayıtlı olan etkinlikleri key, time, date, title ve description olarak ayrıştırmak için kullanacağımız sınıf.
FirstService –> Zamanı gelen bir etkinliği akıllı saate gönderen servis sınıfı.
MainActivity –> Mobil uygulama anasayfası. Bu sınıf ile kayıtlı etkinlikleri listeleyebilir, yeni bir etkinlik ekleyebilir ve var olan bir etkinliği silebiliriz.
SaveActivity –> Faaliyet kaydını yapmayı sağlayan sınıf. Bu sınıfa MainActivity üzerinden erişerek etkinlik kaydını yapacağız.
Mevcut sınıflar hakkında bu kısa bilgilendirmeden sonra bu sırayı takip ederek kodlarımızı paylaşabiliriz.
Kütüphanelerin Eklenmesi
Mobil uygulamayı geliştirmek için öncelikle aşağıda verilen dosyayı açalım.
Dosya yapısının build.gradle <mobile> olduğuna dikkat ediniz. Bu gradle dosyası doğrudan mobil uygulamayı ilgilendiriyor.
Dosyayı açınız ve aşağıda verilen alanı ilgili satırları ekleyiniz.
dependencies { ... compile 'com.google.firebase:firebase-database:15.0.0' compile 'com.google.firebase:firebase-crash:15.0.2' compile 'com.google.firebase:firebase-auth:15.1.0' compile 'com.android.support:cardview-v7:21.0.3' compile 'com.android.support:recyclerview-v7:21.0.3' compile 'com.google.android.gms:play-services-wearable:15.0.0' }
Yukarıda eklediğimiz satırlar ile Firebase, CardView, RecyclerView ve son olarak Wearable servisi ile ilgili kütüphaneleri eklemiş oluruz. Firebase işlemlerini daha önce sizlerle paylaştığım Android Things uygulamasında bulabilirsiniz. Burada o kısma girmeyeceğim. Çünkü bahsi geçen yazımda ilgili bilgileri ayrıntılı olarak anlattım. CardView ve RecyclerView kütüphanelerini, kayıtlı olan faaliyetleri listelemek için kulanacağız. Wearable servisi ise akıllı telefondan akıllı saate veri senkronizasyonunu gerçekleştirmek için ekledik.
AndroidManifest.xml
Kütüphaneleri ekledikten sonra AndroidManifest dosyasına aşağıdaki izinleri ekleyiniz.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mas.smartmirror"> <!--İhtiyaç duyulan izinleri ekledik: ACCESS_WIFI_STATE, INTERNET, WAKE_LOCK ve ACCESS_NETWORK_STATE--> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <!--Activity bileşeninde olduğu gibi servislerde manifest dosyasına eklenmelidir. Servis tanımlamak için <service> etiketi kullanılır. --> <service android:name=".FirstService" /> <activity android:name=".MainActivity" android:label="@string/title_activity_main" android:theme="@style/AppTheme.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".SaveActivity" android:label="@string/title_activity_save" android:theme="@style/Theme.AppCompat.Light.Dialog" /> </application> </manifest>
Şimdi kodlara geçebiliriz.
adapter/MyAdapter
Firebase ortamından alınan faaliyetlerin listelenmesini sağlayan sınıftır. Adapter’lar, veri ile görünüm arasındaki bağlantıyı sağlar. RecyclerView kontrolü, esnek bir kullanıma sahip olduğu için RecyclerView.Adapter sınıfını kullanarak bir Adapter geliştirdik.
package com.mas.smartmirror.adapter; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import com.google.firebase.database.DatabaseReference; import com.mas.smartmirror.R; import com.mas.smartmirror.models.Note; import java.util.ArrayList; /*RecyclerView.Adapter: Adapter oluşturmak için bu sınıf kullanılır. Adapter sınıfları, veri ile AdapterView arasındaki bağlantıyı sağlar.*/ public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { Context ctx; private ArrayList<Note> activities = new ArrayList<>(); DatabaseReference reference; /*Kurucu metot ile ihtiyaç duyulan parametreler alınır.*/ public MyAdapter(Context ctx, ArrayList<Note> activities, DatabaseReference reference) { this.ctx = ctx; this.activities = activities; this.reference = reference; } /*RecyclerView ve ListView içerisinde periyodik olarak findViewById metodunu çağırmak performansı düşürebilir. Bundan dolayı bu işlem için ViewHolder sınıfı kullanılır. Bu sınıf ile her bir görünüme tag etiketi atanabilir. Bu etiket ile çok kolay bir şekilde erişim sağlanır. Yani her defasında findViewById metodunu çağrımak zorunda kalmayız.*/ class ViewHolder extends RecyclerView.ViewHolder { public TextView tvTimeDate; public TextView tvTitleDescription; ImageView imgView; public ViewHolder(View v) { super(v); tvTimeDate = v.findViewById(R.id.tvTimeDate); tvTitleDescription = v.findViewById(R.id.tvTitleDescription); imgView = v.findViewById(R.id.imgView); } } /*Her bir satır temsil edecek arayüz seçilir. Burada single_activites arayüzünü geliştirdik.*/ @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { /*View nesnesi oluşturulur.*/ View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.single_activites, parent, false); /*ViewHolder sınıfından bir nesne türetilir. Parametre olarak View nesnesi atanır*/ ViewHolder vh = new ViewHolder(v); /*ViewHolder ile gelen vh nesnemiz döndürülür.*/ return vh; } /*Her bir görünümün içeriği belirlenir.*/ @Override public void onBindViewHolder(ViewHolder viewHolder, final int position) { /*Her bir kontrol için gelen değerler, position kullanılarak set edilir.*/ viewHolder.tvTimeDate.setText(activities.get(position).getTime() + "\n" + activities.get(position).getDate()); viewHolder.tvTitleDescription.setText(activities.get(position).getTitle() + "\n" + activities.get(position).getDescription()); viewHolder.imgView.setTag(viewHolder); /*single_activites'de bulunan ImageView kontrolüne click olayı tanımlanır. Tıklandığı zaman ilgili etkinlik Firebase ortamında ve cihazdan silinir.*/ viewHolder.imgView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { reference.child(activities.get(position).getKey()).removeValue(); activities.remove(position); updateList(activities); } }); } /*Herhangi bir veri eklendiği veya silindiği zaman listenin güncellenmesini sağlar. */ public void updateList(ArrayList<Note> activities) { activities = activities; notifyDataSetChanged(); } /*Kayıtlı faaliyetlerin sayısı döndürülür*/ @Override public int getItemCount() { return activities.size(); } }
Adapter ile verileri listelerken her bir satır için ortak bir arayüz tasarımı oluşturduk. Arayüz kodları aşağıdaki gibidir.
single_activites.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:id="@+id/card_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_margin="3dp" card_view:cardBackgroundColor="#fff"> <LinearLayout android:id="@+id/target_info" android:layout_width="match_parent" android:layout_height="120dip" android:orientation="horizontal" android:padding="5dp"> <!--Kayıtlı faaliyetin saat ve tarih bilgisi--> <TextView android:id="@+id/tvTimeDate" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_margin="2dp" android:layout_weight="0.3" android:gravity="left" android:text="IoT-ignite" android:textColor="@color/colorAccent" android:theme="@style/textStyle" /> <!--Kayıtlı faaliyetin başlık ve açıklama bilgisi. --> <TextView android:id="@+id/tvTitleDescription" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_margin="2dp" android:layout_weight="0.6" android:gravity="left" android:text="Ocak" android:textColor="@android:color/black" android:theme="@style/textStyle" /> <!--Seçili faaliyeti silmeyi sağlayacak icon --> <ImageView android:id="@+id/imgView" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_margin="2dp" android:layout_weight="0.1" android:gravity="left" android:src="@drawable/ic_delete_black_24dp" android:visibility="visible" /> </LinearLayout> </android.support.v7.widget.CardView>
Yukarıdaki arayüz ile birlikte veriler aşağıdaki gibi listelenecektir.
DatePickerFragment
Kullanıcı yeni bir etkinlik kayıt ederken tarih seçmesini istediğimizde DialogFragment sınıfını kullanabilirsiniz. DatePickerFragment ile aşağıda gösterildiği üzere kullanıcıdan tarih seçmesini isteyen bir arayüz oluşturacağız.
package com.mas.smartmirror.dateandtime; import android.app.DatePickerDialog; import android.app.Dialog; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.widget.DatePicker; import android.widget.TextView; import com.mas.smartmirror.R; import java.util.Calendar; /*DialogFragment: Belirli bir işlemi iletişim penceresi olarak kullanıcıya sunmayı sağlayan sınıf. DatePickerDialog.OnDateSetListener: Kullanıcının bir tarih seçtikten sonra seçilen değerin kullanılmasını sağlayan arayüz. Bu arayüzü uyguladıktan sonra onDateSet() metodunu eklemeniz gerekiyor.*/ public class DatePickerFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener { TextView tvDate; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); /*Bu fragment bileşenine SaveActivity sınıfından erişeceğiz. Amacımız kullanıcı tarih seçtikten sonra SaveActivity'de bulunan tvDate id bilgisine sahip kontrolde tarih bilgisini göstermektir. Fragment bileşeninden bu kontrole ulaşabilmek için getActivity() metodunu kullanırız. */ tvDate=getActivity().findViewById(R.id.tvDate); } /*Fragment için bir Dialog oluşturulur.*/ @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Calendar c = Calendar.getInstance(); /*Sistemden alınan yıl, ay ve gün bilgilerine uyumlu olan bir dialog oluşturmayı sağlar.*/ int year = c.get(Calendar.YEAR); int month = c.get(Calendar.MONTH); int day = c.get(Calendar.DAY_OF_MONTH); /*SaveActivity arayüzünde iletişim penceresi halinde olan bir takvim başlatılır.*/ return new DatePickerDialog(getActivity(), this, year, month, day); } /*Kullanıcı tarih seçimini yapıp onayladıktan sonra bu metot çağrılır.*/ public void onDateSet(DatePicker view, int year, int month, int day) { /*Seçilen tarih bilgisi textView kontrolünde gösterilir. Burada gün, ay ve yıl bilgileri arasında nokta ekledik.*/ ++month; String month1 = month<10 ? "0"+month : ""+month; String day1 = day<10 ? "0"+day : ""+day; tvDate.setText(day1+"."+month1+"."+year); } }
Uygulamayı çalıştırıp tarihi seçtikten sonra sonuç aşağıdaki gibi olur.
TimePickerFragment
Kullanıcı yeni bir etkinlik kayıt ederken saati seçmesini istediğimizde DialogFragment sınıfını kullanabilirsiniz. TimePickerFragment ile aşağıda gösterildiği üzere kullanıcıdan saati seçmesini isteyen bir arayüz oluşturacağız.
package com.mas.smartmirror.dateandtime; import android.app.Dialog; import android.app.TimePickerDialog; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.text.format.DateFormat; import android.widget.TextView; import android.widget.TimePicker; import com.mas.smartmirror.R; import java.util.Calendar; /*DialogFragment: Belirli bir işlemi iletişim penceresi olarak kullanıcıya sunmayı sağlayan sınıf. TimePickerDialog.OnTimeSetListener: Kullanıcının bir saat seçtikten sonra seçilen değerin kullanılmasını sağlayan arayüz. Bu arayüzü uyguladıktan sonra onTimeSet() metodunu eklemeniz gerekiyor.*/ public class TimePickerFragment extends DialogFragment implements TimePickerDialog.OnTimeSetListener { TextView tvTime; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); /*Bu fragment bileşenine SaveActivity sınıfından erişeceğiz. Amacımız kullanıcı saati seçtikten sonra SaveActivity'de bulunan tvTime id bilgisine sahip kontrolde saat bilgisini göstermektir. Fragment bileşeninden bu kontrole ulaşabilmek için getActivity() metodunu kullanırız. */ tvTime=getActivity().findViewById(R.id.tvTime); } /*Fragment için bir Dialog oluşturulur.*/ @Override public Dialog onCreateDialog(Bundle savedInstanceState) { /*Sistemden alınan saat ve dakika bilgilerine uyumlu olan bir dialog oluşturmayı sağlar.*/ final Calendar c = Calendar.getInstance(); int hour = c.get(Calendar.HOUR_OF_DAY); int minute = c.get(Calendar.MINUTE); return new TimePickerDialog(getActivity(), this, hour, minute, DateFormat.is24HourFormat(getActivity())); } /*Kullanıcı saat seçimini yapıp onayladıktan sonra bu metot çağrılır.*/ public void onTimeSet(TimePicker view, int hourOfDay, int minute) { /*Seçilen saat bilgisi textView kontrolünde gösterilir. Burada saat ve dakika bilgileri arasında (:) ekledik.*/ String hour1 = hourOfDay<10 ? "0"+hourOfDay : ""+hourOfDay; String minute1 = minute<10 ? "0"+minute : ""+minute; tvTime.setText(hour1+":"+minute1); } }
Uygulamayı çalıştırıp saati seçtikten sonra sonuç aşağıdaki gibi olur.
Note
Firebase veritabanından alınan faaliyetlerin tarih, saat, başlık ve açıklama olarak ayrıştırılmasını sağlayacağımız sınıf. Böylece faaliyet bilgileri üzerinde daha rahat çalışabiliriz.
package com.mas.smartmirror.models; /*Kayıtlı olan faaliyetleri Note sınıfında bulunan aşağıdaki parametrelere ayrıştıracağız.*/ public class Note { String key; String time; String date; String title; String description; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getTime() { return time; } public void setTime(String time) { this.time = time; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
FirstService
Mobil uygulamayı çalıştırdıktan sonra bu servis çalışmaya başlayacak ve varsa günlük faaliyet veya faaliyetleri akıllı saate gönderecek. Böylece kullanıcının günlük etkinliklerden haberdar olması sağlanacak.
FirstService kodları şu şekildedir:
package com.mas.smartmirror; import android.app.Service; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.support.annotation.Nullable; import android.widget.Toast; import com.google.android.gms.wearable.DataClient; import com.google.android.gms.wearable.DataMap; import com.google.android.gms.wearable.PutDataMapRequest; import com.google.android.gms.wearable.PutDataRequest; import com.google.android.gms.wearable.Wearable; import com.google.firebase.database.ChildEventListener; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; import com.mas.smartmirror.models.Note; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Timer; import java.util.TimerTask; /*FirstService sınıfını Service üst sınıfından türettik. Servisler arkaplanda çalışan ve arayüzleri olamayan uygulama bileşenleridir. ChildEventListener: Firebase veritabanında bulunan verilere erişmek veya veri değişimlerini algılamak için bu arayüzü uyguladık. Bu arayüzü uyguladıktan sonra aşağıdaki metotların eklenmesi gerek: onChildAdded onChildChanged onChildRemoved onChildMoved onCancelled */ public class FirstService extends Service implements ChildEventListener { private FirebaseDatabase mDatabase; private DatabaseReference mReference; private ArrayList<Note> mNotes = new ArrayList<>(); private ArrayList<String> mTodayNote; private HashMap<String, Note> mMap = new HashMap<String, Note>(); @Nullable @Override public IBinder onBind(Intent intent) { return null; } /*onStartCommand: Activity gibi bir bileşenden startService() metodu kullanılarak bir servis başlatıldığı zaman sistem onStartCommand() metodunu başlatır. Bu metot servisi süresiz olarak başlatmaya yarar. Bu şekilde başlattığınız bir metodu sizin kapatmanız gerekmektedir. */ @Override public int onStartCommand(Intent intent, int flags, int startId) { Toast.makeText(this, "Servis başlatıldı.", Toast.LENGTH_SHORT).show(); getFirebase(); /*mynotes JSON dizisinde meydana gelen silme, ekleme vb. olayları algılayan bir olay dinleyici tanımladık.*/ mReference.addChildEventListener(this); /*Kayıtlı olan faaliyetleri sürekli kontrol etmek için handler ve timer yapılarını kullandık.*/ final Handler handler1 = new Handler(); Timer timer1 = new Timer(); timer1.schedule(new TimerTask() { @Override public void run() { handler1.post(new Runnable() { @Override public void run() { /*Firebase ortamında bulunan faaliyetlerin tarih bilgisi sürekli kontrol edilir. Eğer uygun bir faaliyet varsa sendWatch metodu ile faaliyet bilgisi akıllı saate gönderilir.*/ mTodayNote = new ArrayList<>(); for (int i = 0; i < mNotes.size(); i++) { if (getCurrentDate().equalsIgnoreCase(mNotes.get(i).getDate())) { mTodayNote.add(mNotes.get(i).getTime() + "\n" + mNotes.get(i).getTitle()); } } if (mTodayNote != null) { if (mTodayNote.size() != 0) sendWatch(mTodayNote); } } }); } }, 0, 10000); return START_STICKY; } /*onDestroy:Bir servis uzun bir süre kullanılmadığı zaman veya kullanıcı servisin yok edilmesini istediği zaman, sistem bu metodu başlatır. */ @Override public void onDestroy() { super.onDestroy(); Toast.makeText(this, "Servis yok edildi.", Toast.LENGTH_SHORT).show(); } private void getFirebase() { /*Firebase veritabanına erişim sağlanır.*/ mDatabase = FirebaseDatabase.getInstance(); /*Veritabanında bulunan mynotes JSON dizisine erişim sağlanır.*/ mReference = mDatabase.getReference("mynotes"); } /*Firebase ortamından alınan herbir faaliyet Note yapısına ayrıştırılır*/ public Note parseNote(DataSnapshot item) { /*Gelen faaliyet bilgisi key ve value bilgisi alınır değişkenlere atanır.*/ String note = item.getValue(String.class); String key = item.getKey(); /*Hem mobil hem de web uygulamasında tarih, saat, başlık ve açıklam verilerini - karakteri ile birleştirdik. Burada - karakteri yardımıyla value kısmının bir dizi olarak parçalanması sağlanır.*/ String[] separated = note.split("-"); /*Dizinin ilk sırasında tarih bilgisi bulunur.*/ String date = separated[0]; /*Dizinin ikinci sırasında zaman bilgisi bulunur.*/ String time = separated[1]; /*Dizinin üçüncü sırasında başlık bilgisi bulunur.*/ String title = separated[2]; /*Dizinin son sırasında açıklama bilgisi bulunur.*/ String description = separated[3]; /*Note sınıfından bir nesne oluşturup yukarıda elde ettiğimiz verileri ilgili parametrelere atadık.*/ Note n = new Note(); n.setKey(key); n.setTime(time); n.setDate(date); n.setTitle(title); n.setDescription(description); /*Son olarak faaliyeti yani Note nesnesini return ile gönderdik.*/ return n; } /*sendWatch metodu, akıllı telefondan akıllı saate veri göndermeyi sağlar.*/ private void sendWatch(ArrayList<String> n) { /*DataClient sınıf ile Wearable Data Layer'a erişim sağlanır*/ DataClient mDataClient = Wearable.getDataClient(getApplicationContext()); /*DataMap: Öncelikle DataMap yapısını oluşturmalıyız. Bu yapı içerisine, akıllı saate göndermek istediğimiz değerleri eklemeliyiz.*/ DataMap dataMap = new DataMap(); /*DataMap sınıfı yapı olarak Bundle sınıfında benzer. Veriler key-value olarak tutulur. key ifadesinin bu veriye erişmek için kullanacağız. Burada key bilgisi note olarak belirledik.*/ dataMap.putStringArrayList("note", n); /*DataMap oluşturduktan sonra PutDataMapRequest nesnesi oluşturmalıyız. Bu nesnenin temel amacı, iletilecek verinin path yani yol bilgisini belirtmektir. Bunun için create() metodu kullanılır. Path bilgisini akıllı saatte bu veriye erişmek için kullanacağız. setUrgent(): Eğer yapılacak işlem çok önemli ise setUrgent() metodu ile işlemin acil olduğunu belirtmelisiniz. setUrgent() metodu çağırılmadığı zaman, sistem yapılan istekleri 30 dakika kadar geciktirebilir. Bu maksimum değerdir. Genellikle birkaç dakika içinde işlem yapılır. */ PutDataMapRequest putDataMapRequest = PutDataMapRequest.create("/wearable_data").setUrgent(); /*DataMap ile oluşturulan verilerin, PutDataMapRequest nesnesine eklenmesi için getDataMap() ve putAll() metotlarını kullanırız. İletilecek veriler aşağıdaki gibi nesneye eklenir.*/ putDataMapRequest.getDataMap().putAll(dataMap); /*PutDataMapRequest içinde bulunan verilere sahip olan PutDataRequest nesnesi oluşturulur. */ PutDataRequest putDataReq = putDataMapRequest.asPutDataRequest(); /*Son olarak veriler akıllı saate gönderilir. */ mDataClient.putDataItem(putDataReq); } /*Bu metot mevcut günün tarih bilgisini dd.MM.yyyy formatında hesaplamayı sağlar. Firebase ortamında kayıtlı olan bir verinin date bilgisi bu metot ile elde edilen değere eşit ise akıllı saate bilgi gönderilmesi sağlanır.*/ private String getCurrentDate() { Date date = Calendar.getInstance().getTime(); SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy"); String today = dateFormat.format(date); return today; } /*onChildAdded: Firebase ortamında bulunan mynotes JSON dizisine bir veri eklendiğinde bu metot çağrılır.*/ @Override public void onChildAdded(DataSnapshot dataSnapshot, String s) { /*Eklenen yeni faaliyet önce map sonra listeye eklenir. */ Note note = parseNote(dataSnapshot); mMap.put(dataSnapshot.getKey(), note); mNotes.add(note); } /*onChildChanged: Firebase ortamında kayıtlı bir veride değişim olduğunda çağrılır.*/ @Override public void onChildChanged(DataSnapshot dataSnapshot, String s) { } /*onChildRemoved:Firebase ortamında bulunan mynotes JSON dizisinden bir veri silindiğinde bu metot çağrılır.*/ @Override public void onChildRemoved(DataSnapshot dataSnapshot) { /*Silinen faaliyet map yapısından silinir.*/ mMap.remove(dataSnapshot.getKey()); /*Daha sonra listede bulunan tüm faaliyetler silinir.*/ mNotes.clear(); /*Son olarak map yapısında kalan tüm faaliyetler listeye yeniden eklenir.*/ for (Map.Entry<String, Note> entry : mMap.entrySet()) { mNotes.add(entry.getValue()); } } @Override public void onChildMoved(DataSnapshot dataSnapshot, String s) { } @Override public void onCancelled(DatabaseError databaseError) { } }
MainActivity
MainActivity sınıfını uygulamanın ana sayfasını oluşturmak için kullanacağız. Öncelikle arayüz ile ilgili xml dosyalarının kodlarını paylaşmak istiyorum.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <!--android.support.design.widget.CoordinatorLayout: isminden de anlaşılacağı üzere, bu düzenin temel amacı, içerisinde bulunan görünümleri, koordine etmesidir. Yani; görünümler arasında koordineli bir geçiş yapılmasını sağlamasıdır. Bu düzeni bir iskelet yapısı olarak düşünebilirsiniz. Yapısı aşağıdaki gibidir. <CoordinatorLayout> <AppBarLayout/> <ScrollView/> <FloatingActionButton/> </CoordinatorLayout> android:fitsSystemWindows=”true” : bu özellik ile düzenimiz sistem penceresini tamamen doldurur(status bar yani, durum çubuğu gibi alanları da kapatır. Bu değişimi görmek için API 21 olan bir cihazda deneyiniz.)--> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <!--AppBarLayout: LinearLayout düzenine benzeyen ve görünümleri dikey olarak hizalayan bir düzendir. İçerik kaydırıldığı anda, belirli bazı parametrelerle görünümleri yönetebilir.--> <android.support.design.widget.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="200dp" android:fitsSystemWindows="true" android:theme="@style/AppTheme.AppBarOverlay"> <!--CollapsingToolbarLayout: Toolbar nesnesini içine alan bir düzendir. İçerik yukarı doğru hareket ettirildiği zaman içinde bulunan ImageView kontrolünü gizler ve ToolBar nesnesini başlık olarak ayarlar. İçerik yukarı sürüklendiği zaman, ImageView kontrolünün gizlenmesi için app:contentScrim=”?attr/colorPrimary” özelliği eklenmelidir. layout_scrollFlags: Bu özellik aşağıdaki değerleri alabilir. scroll: içerik yukarı doğru sürüklendiğinde AppBar tamamen gizlenir. exitUntilCollapsed: scroll ile kullanıldığında, resim gizlenir ve toolbar başlık olarak ayarlanır.--> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <!--Resim göstermek isterseniz.--> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:background="@drawable/smart_mirror" android:scaleType="centerCrop" android:visibility="invisible" app:layout_collapseMode="parallax" /> <!--Toolbar: AppBar Eklemek için kullanılır. Burada en önemli özellik app:layout_collapseMode=”pin” özelliğidir. Eğer bu ayarlamayı yapmazsak, AppBar yukarı sürüklendiğinde, menü simgesi gizlenir.--> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="wrap_content" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <!--NestedScrollView: ScrollView kontrolünün eksiklerini gideren daha kullanışlı ve esnek bir scroll kontrolüdür. Özellikle bu tür uygulamalar için çok kullanışlıdır. Aradaki farkı daha iyi anlamak için android.support.v4.widget.NestedScrollView ifadesini silip ScrollView yazıp, uygulamanızı çalıştırınız. Diğer bir kullanım sebebi, tek bir ekranda iki adet ScrollView kullanılmaz. Fakat yine de ihtiyaç olursa bu kontrol ScrollView içerisinde oluşturulabilir. ScrollView ile benzerliği, içerisinde tek bir ana kontrolün olmasına izin vermesidir. layout_behavior: Verilen değer ile bu özellik eklenmediği zaman, içerik uygulamanın en tepesinde gösterilir.--> <android.support.v4.widget.NestedScrollView xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!--Firebase ortamından veriler yüklenene kadar kullanıcıya gösterilir--> <ProgressBar android:id="@+id/pbLoading" style="?android:attr/progressBarStyle" android:layout_width="match_parent" android:layout_height="wrap_content" /> <!--Faaliyetleri listeleyeceğimiz kontrol --> <android.support.v7.widget.RecyclerView android:id="@+id/my_recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" android:scrollbars="vertical" /> </LinearLayout> </android.support.v4.widget.NestedScrollView> <!-- FloatingActionButton: Yüzen eylem butonu olarak bilinir. Bu menüde öncelikli işlemler bulunur. Bu buton her zaman için ekranda gösterilmek için tasarlandığı için yüzen buton olarak isimlendirilir. Buradaki kullanım amacı SaveActivity etkinlğini başlatarak faaliyet kaydını yapmayı sağlamaktır.--> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="25dp" android:src="@drawable/ic_add"/> </android.support.design.widget.CoordinatorLayout>
MainActivity sınıfı için yukarıdaki arayüzü kullanacağız. Tasarımdan sonra kodlarımız aşağıdaki gibidir.
package com.mas.smartmirror; import android.content.Intent; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.View; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.widget.ProgressBar; import com.google.android.gms.wearable.DataClient; import com.google.android.gms.wearable.DataMap; import com.google.android.gms.wearable.PutDataMapRequest; import com.google.android.gms.wearable.PutDataRequest; import com.google.android.gms.wearable.Wearable; import com.google.firebase.database.ChildEventListener; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; import com.mas.smartmirror.adapter.MyAdapter; import com.mas.smartmirror.models.Note; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Map; public class MainActivity extends AppCompatActivity implements ChildEventListener, View.OnClickListener { private Toolbar mToolbar; private ProgressBar mProgressBar; private FloatingActionButton mFab; private RecyclerView mRecyclerView; private RecyclerView.Adapter mAdapter; private RecyclerView.LayoutManager mLayoutManager; private FirebaseDatabase mDatabase; private DatabaseReference mReference; private ArrayList<Note> mNotes = new ArrayList<>(); private ArrayList<String> mTodayNote = new ArrayList<>(); private HashMap<String, Note> mMap = new HashMap<String, Note>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getControls(); getFirebase(); setRecyclerView(); setEventListener(); /*Aşağıdaki kodlar ile FirstService sınıfı yani servis başlatılır.*/ Intent i = new Intent(MainActivity.this,FirstService.class); startService(i); } /*activity_main'de bulunan kontrollere erişim sağlanır.*/ private void getControls() { mToolbar = findViewById(R.id.toolbar); setSupportActionBar(mToolbar); mProgressBar = findViewById(R.id.pbLoading); mRecyclerView = findViewById(R.id.my_recycler_view); mFab = findViewById(R.id.fab); } private void getFirebase() { /*Firebase veritabanına erişim sağlanır.*/ mDatabase = FirebaseDatabase.getInstance(); /*Veritabanında bulunan mynotes JSON dizisine erişim sağlanır.*/ mReference = mDatabase.getReference("mynotes"); } /*RecyclerView için ihtiyaç duyulan özellikleri set eden metodumuz*/ private void setRecyclerView() { /*Bu özelliği set ettiğimizde performansı arttırır. Eğer içeriğin değişmesi, RecyclerView düzen boyutunu değiştirmiyorsa bu özelliği set edebilirsiniz.*/ mRecyclerView.setHasFixedSize(true); /*Her bir satırın nasıl hizalanacağı belirlenir. Her satır dikey olarak hizalanır.*/ mLayoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(mLayoutManager); /*Adapter nesnesi oluşturulur. Parametre olarak faaliyetleri tutan liste kullanılır.*/ mAdapter = new MyAdapter(getApplicationContext(), mNotes, mReference); /*RecyclerView, adapter ile doldurulur.*/ mRecyclerView.setAdapter(mAdapter); } private void setEventListener() { /*mynotes JSON dizisinde meydana gelen silme, ekleme vb. olayları algılayan bir olay dinleyici tanımladık.*/ mReference.addChildEventListener(this); /*FloatingActionButton için click olayı tanımladık.*/ mFab.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.fab: /*FloatingActionButton kontrolüne tıklandığı zaman SaveActivity arayüzü başlatılır. Kullanıcı bu arayüzü kullanarak Firebase ortamına faaliyet kaydını yapabilir.*/ Intent i = new Intent(MainActivity.this, SaveActivity.class); startActivity(i); break; } } /*Firebase ortamından alınan herbir faaliyet Note yapısına ayrıştırılır*/ public Note parseNote(DataSnapshot item) { /*Gelen faaliyet bilgisi key ve value bilgisi alınır değişkenlere atanır.*/ String note = item.getValue(String.class); String key = item.getKey(); /*Hem mobil hem de web uygulamasında tarih, saat, başlık ve açıklam verilerini - karakteri ile birleştirdik. Burada - karakteri yardımıyla value kısmının bir dizi olarak parçalanması sağlanır.*/ String[] separated = note.split("-"); /*Dizinin ilk sırasında tarih bilgisi bulunur.*/ String date = separated[0]; /*Dizinin ikinci sırasında zaman bilgisi bulunur.*/ String time = separated[1]; /*Dizinin üçüncü sırasında başlık bilgisi bulunur.*/ String title = separated[2]; /*Dizinin son sırasında açıklama bilgisi bulunur.*/ String description = separated[3]; /*Note sınıfından bir nesne oluşturup yukarıda elde ettiğimiz verileri ilgili parametrelere atadık.*/ Note n = new Note(); n.setKey(key); n.setTime(time); n.setDate(date); n.setTitle(title); n.setDescription(description); /*Son olarak faaliyeti yani Note nesnesini return ile gönderdik.*/ return n; } /*onChildAdded: Firebase ortamında bulunan mynotes JSON dizisine bir veri eklendiğinde bu metot çağrılır.*/ @Override public void onChildAdded(DataSnapshot dataSnapshot, String s) { /*Eklenen yeni faaliyet önce map sonra listeye eklenir. */ Note note = parseNote(dataSnapshot); mMap.put(dataSnapshot.getKey(), note); mNotes.add(note); /*Listenin güncellenmesi sağlanır. Böylece yeni bir faaliyet eklediğinizde arayüzde bulunan listenin uzadığını görürsünüz.*/ mAdapter.notifyDataSetChanged(); mProgressBar.setVisibility(View.GONE); mRecyclerView.setVisibility(View.VISIBLE); } /*onChildChanged: Firebase ortamında kayıtlı bir veride değişim olduğunda çağrılır.*/ @Override public void onChildChanged(DataSnapshot dataSnapshot, String s) { } /*onChildRemoved:Firebase ortamında bulunan mynotes JSON dizisinden bir veri silindiğinde bu metot çağrılır.*/ @Override public void onChildRemoved(DataSnapshot dataSnapshot) { /*Silinen faaliyet map yapısından silinir.*/ mMap.remove(dataSnapshot.getKey()); /*Daha sonra listede bulunan tüm faaliyetler silinir.*/ mNotes.clear(); /*Son olarak map yapısında kalan tüm faaliyetler listeye yeniden eklenir.*/ for (Map.Entry<String, Note> entry : mMap.entrySet()) { mNotes.add(entry.getValue()); } /*Silme işleminin arayüzde bulunan ve kullanıcaya gösterilen listeye uygulanmasını sağlar. Eğer bunu yapmazsanız silinen veri arayüzde listelenmeye devam eder.*/ mAdapter.notifyDataSetChanged(); } @Override public void onChildMoved(DataSnapshot dataSnapshot, String s) { } @Override public void onCancelled(DatabaseError databaseError) { } }
Bu kodları çalıştırdıktan sonra arayüz aşağıdaki gibi olur.
Son olarak faaliyet kaydını yapan SaveActivity sınıfının kodlarını paylaşıp bu makaleyi sonlandıralım.
SaveActivity
Kullanıcının faaliyet kaydını yapmasını sağlayan sınıftır. Bu sınıf için arayüz kodları aşağıdaki gibidir.
activity_save.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:padding="8dp" tools:context="com.mas.smartmirror.SaveActivity"> <!--Kullanıcının saat seçmesini sağlayan buton. Buna tıkladığımız zaman TimePickerFragment fragment bileşeni kullanıcıya gösterilir. Kullanıcı buradan saat seçebilir.--> <ImageButton android:id="@+id/imgTimePicker" android:layout_width="@dimen/ic_dimen" android:layout_height="@dimen/ic_dimen" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/etDescription" android:layout_marginTop="15dp" android:background="@drawable/ic_alarm_black_24dp" android:text="@string/activity_save_image_button_time" /> <!--Kullanıcının tarih seçmesini sağlayan buton. Buna tıkladığımız zaman DatePickerFragment fragment bileşeni kullanıcıya gösterilir. Kullanıcı buradan tarih seçebilir.--> <ImageButton android:id="@+id/imgDatePicker" android:layout_width="@dimen/ic_dimen" android:layout_height="@dimen/ic_dimen" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:layout_alignTop="@+id/imgTimePicker" android:layout_marginEnd="117dp" android:layout_marginRight="117dp" android:background="@drawable/ic_date_range_black_24dp" android:text="@string/activity_save_image_button_date" /> <!--Bu butona basıldığı zaman led yanar veya led_status ifadesine true değeri atanır--> <Button android:id="@+id/btnSave" android:layout_width="match_parent" android:background="@color/colorAccent" android:textColor="@color/saveBtnTextColor" android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:layout_below="@+id/tvTime" android:layout_marginEnd="8dp" android:layout_marginRight="8dp" android:layout_marginTop="16dp" android:text="@string/activity_save_btn_save" /> <!--Faaliyet için başlık girmemizi sağlayan kontrol --> <EditText android:id="@+id/etTitle" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:background="@drawable/edittext_shape" android:ems="10" android:fontFamily="sans-serif-condensed" android:hint="@string/activity_save_title" android:inputType="textPersonName" android:textColor="#aa001c" /> <!--Faaliyet için açıklama girmemizi sağlayan kontrol --> <EditText android:id="@+id/etDescription" android:layout_width="match_parent" android:layout_height="200dp" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/etTitle" android:layout_marginTop="12dp" android:background="@drawable/edittext_shape" android:ems="10" android:fontFamily="sans-serif-condensed" android:gravity="left|top" android:hint="@string/activity_save_description" android:inputType="textMultiLine" android:textAlignment="gravity" android:textColor="#aa001c" android:textDirection="firstStrong" /> <!--Faaliyet için seçilen saat bilgisi buraya yazılır --> <TextView android:id="@+id/tvTime" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@+id/imgTimePicker" android:layout_toEndOf="@+id/imgTimePicker" android:layout_toRightOf="@+id/imgTimePicker" android:text="@string/activity_save_image_button_time" android:textColor="@color/colorAccent" android:textSize="15sp" /> <!--Faaliyet için seçilen tarih bilgisi buraya yazılır --> <TextView android:id="@+id/tvDate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/tvTime" android:layout_alignBottom="@+id/tvTime" android:layout_alignLeft="@+id/imgDatePicker" android:layout_alignStart="@+id/imgDatePicker" android:layout_marginLeft="37dp" android:layout_marginStart="37dp" android:text="@string/activity_save_image_button_date" android:textColor="@color/colorAccent" android:textSize="15sp" /> </RelativeLayout>
SaveActivity sınıfı kodları aşağıdaki gibidir.
package com.mas.smartmirror; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; import android.widget.TextView; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; import com.mas.smartmirror.dateandtime.DatePickerFragment; import com.mas.smartmirror.dateandtime.TimePickerFragment; public class SaveActivity extends AppCompatActivity implements View.OnClickListener { private Button mBtnSave; private ImageButton mTimePicker, mDatePicker; private FirebaseDatabase mDatabase; private DatabaseReference mReference; private TextView mTime, mDate; private EditText mTitle, mDescription; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activitiy_save); getControls(); getFirebase(); setEventListener(); } /*activity_save arayüzünde bulunan kontrollere erişim sağlarız.*/ private void getControls() { mTime = findViewById(R.id.tvTime); mDate = findViewById(R.id.tvDate); mTitle = findViewById(R.id.etTitle); mDescription = findViewById(R.id.etDescription); mBtnSave = findViewById(R.id.btnSave); mTimePicker = findViewById(R.id.imgTimePicker); mDatePicker = findViewById(R.id.imgDatePicker); } private void getFirebase() { /*Firebase veritabanına erişim sağlanır.*/ mDatabase = FirebaseDatabase.getInstance(); /*Veritabanında bulunan mynotes JSON dizisine erişim sağlanır.*/ mReference = mDatabase.getReference("mynotes"); } /*Butonlara tıklama olayı tanımladık*/ private void setEventListener() { mBtnSave.setOnClickListener(this); mTimePicker.setOnClickListener(this); mDatePicker.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btnSave: /*btnSave id bilgisine sahip butona tıklandığı zaman faaliyet kaydı yapılır.*/ saveMyNote(); break; case R.id.imgTimePicker: /*imgTimePicker id bilgisine sahip butona tıklandığı zaman saat seçimini yapmayı sağlayan Fragment açılır.*/ showTimePickerDialog(); break; case R.id.imgDatePicker: /*imgDatePicker id bilgisine sahip butona tıklandığı zaman tarih seçimini yapmayı sağlayan Fragment açılır.*/ showDatePickerDialog(); break; } } /*Kullanıcının girdiği başlık, açıklama, saat ve tarih bilgileri Firebase ortamına kayıt edilir.*/ private void saveMyNote() { long i = System.currentTimeMillis(); DatabaseReference newNote = mReference.child(String.valueOf(i)); /*Veriler arasına - karakteri eklediğimize dikkat ediniz. Daha sonra bu karakteri kullanarak split metodu ile bir dizi oluşturacağız.*/ newNote.setValue(mDate.getText().toString() + "-" + mTime.getText().toString() + "-" + mTitle.getText().toString() + "-" + mDescription.getText().toString()); /*Kayıt işleminden sonra SaveActivity sonlandırılır.*/ finish(); } /*Kullanıcının saat seçebilmesini sağlayan showTimePickerDialog fragment bileşeni başlatılır.*/ public void showTimePickerDialog() { DialogFragment newFragment = new TimePickerFragment(); newFragment.show(getSupportFragmentManager(), "timePicker"); } /*Kullanıcının tarih seçebilmesini sağlayan DatePickerFragment fragment bileşeni başlatılır.*/ public void showDatePickerDialog() { DialogFragment newFragment = new DatePickerFragment(); newFragment.show(getSupportFragmentManager(), "datePicker"); } }
Uygulama kodlarını yazdıktan sonra kayıt işlemini yapan arayüz aşağıdaki gibi olur.
Sonraki makalede akıllı saat uygulamasını geliştireceğiz. Görüşmek üzere…