Implementacja prostego narzędzia do obsługi ticketów na platformę Android wraz z synchronizacją z zewnętrznym serwerem bazy danych

Poniżej jest umieszczana automatycznie treść podstrony tickets znajdującej się w namespace projektu. Proszę ją utworzyć i traktować jako stronę startową - tam umieszczamy linki do poszczególnych podstron, które też mają się znajdować w projekcie, np. analiza_wymagan. W namespace mogą też Państwo umieszczać pliki (obrazki, diagramy, archiwa) linkowane na stronie danego projektu. Proszę usunąć ten akapit po zapoznaniu się z jego treścią.

Projekt konceptualny

Sformułowanie zadania projektowego: Projekt jest przeznaczony dla osób realizujących pewne projektu, aby mogły przypisywać sobie wzajemnie zadania do wykonania w formie ticketów. Atutem tego rozwiązania jest implementacja na platformę mobilną Android dzięki czemu możliwe jest przeglądanie ticketów przy użyciu telefonu komórkowego. W przypadku braku dostępu do internetu aplikacja pobiera potrzebne dane z lokalnej bazy danych SQLite, a następnie gdy uzyska połączenie dokonuje synchronizacji z zewnętrzną bazą danych.

Analiza stanu wyjściowego: Przypisywanie zadań dla poszczególnych członków zespołu odbywało się wcześniej za pomocą telefonów czy później poczty elektronicznej. Teraz dostępne są serwisy takie jak assembla.com, które udostępniają funkcjonalność przypisywania ticketów. Innowacyjność zaprezentowanego rozwiązania polega na mobilnym dostępie do bazy danych, a brak dostępu do internetu nie uniemożliwia korzystania z aplikacji.

Analiza wymagań użytkownika (wstępna): Użytkownik ma możliwość przeglądania i dodawania grup roboczych, przeglądania, edycji i dodawania ticketów, wykonania telefonu, napisania wiadomości SMS, wysłania wiadomości e-mail do członka zespołu z poziomu aplikacji, przeglądania oraz dodawania komentarzy do ticketów. Użytkownik może dokonywać tych czynności w trybie offline na lokalnej bazie danych, jak i w trybie online. Praca w trybie online wymaga uwierzytelnienia użytkownika.

Określenie scenariuszy użycia:

Użytkownik chce przejrzeć tickety po raz pierwszy:

  1. Użytkownik wpisuje swój login i hasło.
  2. W przypadku braku dostępu do sieci lub błędnego loginu lub hasła ukazuje się komunikat o niepowodzeniu logowania.
  3. W przypadku poprawnego zalogowania zostają wczytane z bazy danych wszystkie informacje dotyczące ticketów, do których użytkownik powinien mieć dostęp
  4. Wyświetla się lista grup roboczych, których użytkownik jest właścicielem, bądź członkiem.
  5. Dotknięcie wybranej nazwy grupy roboczej powoduje wyświetlenie listy ticketów należących do tej grupy.
  6. Dotknięcie wybranego ticketu powoduje rozwinięcie listy, gdzie widoczne są właściwości danego ticketu.

Użytkownik chce przepisać wybrany ticket na siebie i zmienić jego status:

  1. Użytkownik dotyka wybranej grupy roboczej.
  2. Użytkownik dotyka wybranego ticketu.
  3. Użytkownik przytrzymuje pole, które chce edytować.
  4. Z menu dialogowego użytkownik wybiera „Edit”
  5. W przypadku gdy użytkownik ma dostęp do sieci:
    1. Zmiana zostaje zapisana na serwerze.
    2. W liście ticketów widoczna jest wprowadzona zmiana.
  6. W przypadku gdy użytkownik nie ma dostępu do sieci:
    1. Zmiana zostaje zapisana jedynie w lokalnej bazie danych.
    2. W liście ticketów widoczna jest wprowadzona zmiana.
    3. Jeżeli użytkownik będzie wyświetlał listę ticketów mając już dostęp do sieci i w bazie danych edytowane przez użytkownika pole nie zostało zmienione, wprowadzone przez niego wartości zostaną umieszczone na serwerze. Gdy natomiast pole zostało edytowane na serwerze, w liście ticketów pole to będzie zaznaczone czerwonym kolorem.
    4. Aby rozwiązać konflikt użytkownik przytrzymuje dane pole.
    5. Wybiera „Solve conflict” i zaznacza czy na serwerze ma pozostać to co jest czy też ma zostać wprowadzona wartość z lokalnej bazy danych. Aby wykonać tą operację niezbędne jest oczywiście połączenie z internetem.

Identyfikacja funkcji. Określenie podstawowych funkcji realizowanych w bazie danych.

  • login()
  • get_workspaces()
  • get_tickets()
  • get_usersingroup()
  • get_users()
  • get_comments()
  • add_comment()
  • update_ticket()
  • add_ticket()
  • delete_ticket()

Budowa i analiza diagramu przepływu danych (DFD – Data Flow Diagram)

Projektowanie powiązań (relacji) pomiędzy encjami. Konstrukcja diagramu ERD (Entity-Relationship Diagram)

Projekt diagramów STD (State Transition Diagram – diagramy przejść pomiędzy stanami).

Projekt logiczny

Projektowanie tabel, kluczy, kluczy obcych, powiązań między tabelami, indeksów, etc. w oparciu o zdefiniowany diagram ERD

  CREATE TABLE workgroups ( 
   		idworkgroup INTEGER PRIMARY KEY AUTOINCREMENT, 
   		name VARCHAR(20) NOT NULL, 
   		groupowner VARCHAR(20) NOT NULL 
   		);
  CREATE TABLE users( 
    		login varchar(20) primary key, 
	        password varchar(40) not null,
    		email varchar(30), 
    		phone varchar(10) 
    		);
  CREATE TABLE usersingroup( 
    		login varchar(20),
    		idworkgroup integer, 
    		primary key(login,idworkgroup) 
    		);
  CREATE TABLE tickets( 
    		idticket integer primary key autoincrement, 
    		date date not null, 
    		assignedTo varchar(20) not null, 
    		priority smallint not null, 
    		status smallint not null, 
    		reportedBy varchar(20) not null, 
    		description varchar(100) not null, 
    		idworkgroup integer not null, 
    		time time not null, 
    		summary text not null, 
    		foreign key(assignedTo) references users(login), 
    		foreign key(reportedBy) references users(login), 
    		foreign key(idworkgroup) references workgroups(idworkgroup) 
    		);
  CREATE TABLE comments( 
      	        idcomment integer primary key autoincrement, 
	        idticket integer not null, 
      	        login varchar(20) not null, 
      	        date date not null, 
        	time time not null, 
         	comment text not null, 
                foreign key(idticket) references tickets(idticket), 
        	foreign key(login) references users(login) 
        	);
  CREATE TABLE conflicts( 
    		idticket integer, 
    		field smallint, 
    		my boolean default false, 
    		serv text default null, 
    		primary key(idticket,field), 
    		foreign key(idticket) references tickets(idticket) 
    		);

Analiza zależności funkcyjnych i normalizacja tabel (dekompozycja do 3NF, BCNF, 4NF, 5NF)

Baza danych spełnia 1NF ponieważ każda składowa w każdej kropce jest atomowa i nie da się jej podzielić.

Baza danych spełnia 2NF ponieważ spełnia 1NF oraz każdy element jest zależnie funkcyjny od kluczy poszczególnych tabeli.

Baza danych spełnie 3NF ponieważ spełnia 2NF oraz każdy atrybut jest bezpośrednio zależny od klucza głównego w poszczególnych tabelach.

Projektowanie operacji na danych

Pytanie o hasło w celu zalogowania.

select password from users where login=„?”;

Pobranie grup roboczych, z którymi związany jest użytkownik.

select distinct idworkgroup, name, groupowner from workgroups natural join usersingroup where groupowner=„?” or login=„?”;

Pobranie ticketów dla podanej grupy roboczej.

select * from tickets where idworkgroup = ? or idworkgroup = ? or idworkgroup = ? or … ;

Aktualizacja zadanego pola zadaną wartością dla zadanego ticketu

update tickets set ? = „?” where idticket = ?;

Umieszczenie w bazie danych nowo stworzonego ticketu.

insert into tickets(date,assignedTo,priority,status,reportedBy,description,idworkgroup,time,summary) values(„?”,„?”,?,?,„?”,„?”,?,„?”,„?”);

Pobranie wszystkich użytkowników związanych z zadanymi grupami roboczymi.

select * from users natural join usersingroup where idworkgroup = ? or idworkgroup = ? or idworkgroup = ? or …

Raport końcowy

Serwer http

Wykonanie projektu składało się z dwóch części. Części serwerowej, która ma za zadanie odpytywanie bazy danych. Wykonana została w języku PHP z użyciem pakietu narzędziowego Code Igniter 2.0. Składała się ona z pojedynczego kontrolera, który logował użytkowników, oraz kierował zapytania od zalogowanych użytkowników do modelu, który konstruował kwerendy sql i zwracał tablice wyników. Następnie kontroler otrzymane tablice zapisywał do formatu json i przekazywał klientowi.

Funkcja logująca użytkownika:

function login()
	{
		
		$success = $this->Homemodel->login();
		if ($success)
		{
			$this->session->set_userdata(array('logged_in'=>true));
			$data = array("logged_in" => "true");
			print("[".json_encode($data)."]");
		}
		else
		{
			$data = array("logged_in" => "false");
			print("[".json_encode($data)."]");
		}
	}

oraz funkcja w modelu:

function login()
    {
	$query = $this->db->query('select password from users where login="'.$_REQUEST['login'].'"');
	$array = $query->result();
	
	if(count($array) == 0)
		return false;
	if(strcmp(md5($_REQUEST['password']),$array[0]->password) == 0)
	{
		return true;
	}
	else{
		return false;
	}
	
    }
    

Wszystkie funkcje sprawdzały czy dany użytkownik jest zalogowany. W przypadku niezalogowania zwracany zostawał błąd 404.

function get_tickets(){
		if($this->session->userdata('logged_in') == true)
			print(json_encode($this->Homemodel->get_tickets()));
		else
			show_404();
		
	}

Otrzymanie tego błędu po stronie klienckiej powodowało wyświetlenie okna dialogowego w celu autoryzacji użytkownika.

Serwer odbiera parametry zapytania za pomocą REQUEST[nazwa klucza] i konstruuje odpowiednie zapytanie sql.

function get_tickets()
	{
		$sql = 'select * from tickets where ';
		for($i = 0;$i< $_REQUEST['workgroupCount'];$i++){
			$sql = $sql.'idworkgroup = '.$_REQUEST['idworkgroup_'.$i];
			if($i != $_REQUEST['workgroupCount'] - 1)
				$sql = $sql.' or ';
		}
		$query = $this->db->query($sql);
		return $query->result();
	}

Klient java Android

Klient aplikacji został w całości napisany w języku java przy użyciu Android SDK. W języku java istnieją odpowiednie biblioteki, za pomocą których możliwe jest połączenie z bazą danych. Jednak należy zwrócić uwagę, że w tym przypadku nie jest to serwer aplikacyjny, a mobilna platforma. Użytkownicy mogą używać aplikacji np. w podróży używając sieci komórkowej, która zapewnia im dostęp do internetu. W momentach kiedy urządzenie się przemieszcza musi przełączać się pomiędzy poszczególnymi masztami. Taka charakterystyka używania tych urządzeń uniemożliwia bezpośredniego połączenia z bazą danych ponieważ występują zdecydowanie za duże opóźnienia. Połączenie z bazą danych w przypadku tego projektu zostało wykonane za pomocą protokołu http łączącego się z serwerem www, który był uruchomiony na tej samej maszynie co baza danych, co zapewniało szybki dostęp. Aplikacja kliencka otrzymywała dane od serwera w formacie json co następnie umożliwiało dogodne przeiterowanie po elementach i wprowadzenie ich do lokalnej bazy danych.

Platforma Android udostępnia SQLite dzięki czemu możliwe jest składowanie większych partii danych w relacyjnej bazie danych na urządzeniu. Baza danych składowana jest w pojedynczym pliku umieszczonym w katalogu, do którego użytkownik nie ma dostępu. Rodzi to ten problem, że projektując aplikację korzystającą z SQLite należy używać symulatora (który niestety działa zdecydowanie wolniej niż urządzenie), ponieważ tylko wtedy można odczytać plik bazy danych w celu sprawdzenia poprawności działania aplikacji. Do przeglądania plików SQLite bardzo dobrze działa program SQLite Database Browser.

Ważną częścią aplikacji było stworzenie interfejsu użytkownika. Składa się on z początkowego ekranu logowania, który zostaje wyświetlony w przypadku pierwszego uruchomienia aplikacji. Po poprawnym zalogowaniu następuje wczytanie z serwera wszystkich danych związanych z użytkownikiem i umieszczenie ich w lokalnej bazie danych. Następnym ekranem jest lista grup roboczych, z którymi zalogowany użytkownik ma związek. Najważniejszym ekranem jest rozwijana lista ticketów, zawierające wszystkie tickety. Po dotknięciu danego ticketu zostają wyświetlone jego parametry:

  • Data utworzenia
  • Użytkownik, który stworzył dany ticket
  • Użytkownik, do którego ticket został przypisany
  • Opis ticketu
  • Priorytet
  • Status
  • Komentarze

Dla czytelniejszego odbioru dotknięcie w pole komentarzy wyświetli listę komentarzy związanych z ticketem.

W przypadku gdy użytkownik ma dostęp do sieci, wszystkie akcje jakie podejmuje zostają automatycznie uwzględnione na serwerze. (Działanie to przypomina używanie AJAX w aplikacjach webowych) Aby zobaczyć zmiany, które wprowadzili w tym czasie inni użytkownicy należy odświeżyć widok. Aplikacja umożliwia również pracę offline. W momencie wprowadzenia jakiejkolwiek zmiany zostaje utworzona kopia lokalnej bazy danych, która ma na celu przechowywać jej stan przed wprowadzeniem zmian. Następnie w przypadku uruchomienia aplikacji z dostępem do internetu zostaje porównana kopia bazy danych z bazą danych na serwerze. W przypadku gdy dane pole w serwerowej bazie danych nie zostało zmienione, a użytkownik edytował to pole offline, pole zostanie automatycznie zaktualizowane z wersją z lokalnej bazy danych. Natomiast gdy zmiana została dokonana zarówno na serwerze jak i w lokalnej bazie danych, występuje konflikt zaznaczony w aplikacji kolorem czerwonym. Użytkownik może rozwiązać konflikt wybierając pomiędzy wersją lokalną, a wersją z serwera. W przypadku dokonania synchronizacji kasowana jest kopia bazy danych, a informacje o konfliktach umieszczone są w tabeli konflikty w lokalnej bazie danych.

Głównymi problemami z jakimi należało się zmierzyć przy tworzeniu tej aplikacji był interfejs użytkownika. W systemie Android aby wyświetlić okno dialogowe, które ma za zadanie wprowadzić np nową wartość danego parametru ticketu czy choćby okno progress bar, należy umieścić odpowiedni uchwyt (Handler), oraz oddzielny wątek, który poinformuje ustawiony uchwyt o wprowadzonych danych. Dzieje się tak dlatego, że widok zostaje zablokowany i nie można dokonać w nim żadnych zmian. Wymagane było również wymuszane odświeżanie widoku w celu odpowiedniego wyświetlania konfliktów.

Umieszczenie ticketu w lokalnej bazie danych

private void addTickets(int idticket, String date, String assignedTo,
			int priority, int status, String reportedBy, String description,
			int idworkgroup, String time, String summary) {
		SQLiteDatabase sql = wD.getWritableDatabase();
		ContentValues values = new ContentValues();
		values.put("idticket", idticket);
		values.put("date", date);
		values.put("assignedTo", assignedTo);
		values.put("priority", priority);
		values.put("status", status);
		values.put("reportedBy", reportedBy);
		values.put("description", description);
		values.put("idworkgroup", idworkgroup);
		values.put("time", time);
		values.put("summary", summary);
		sql.insertOrThrow("tickets", null, values);
		sql.close();
	}

Funkcja ta zostaje użyta między innymi w metodzie pośredniczącej pomiędzy lokalną i zewnętrzną bazą danych.

jArray = db.getTickets(idworkgroup);
		try {
			for (int i = 0; i < jArray.length(); i++) {
				JSONObject json_data = jArray.getJSONObject(i);
				Log.d("tickets", json_data.getString("description"));
				addTickets(json_data.getInt("idticket"),
						json_data.getString("date"),
						json_data.getString("assignedTo"),
						json_data.getInt("priority"),
						json_data.getInt("status"),
						json_data.getString("reportedBy"),
						json_data.getString("description"),
						json_data.getInt("idworkgroup"),
						json_data.getString("time"),
						json_data.getString("summary"));
			}
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

Korzysta ona z db.getTickets(), która to pobiera tickety z zewnętrznej bazy danych.

public JSONArray getTickets(ArrayList<Integer> idworkgroups) {

		// the year data to send
		ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
		nameValuePairs.add(new BasicNameValuePair("workgroupCount", String
				.valueOf(idworkgroups.size())));
		for (int i = 0; i < idworkgroups.size(); i++)
			nameValuePairs.add(new BasicNameValuePair("idworkgroup_"
					+ String.valueOf(i), String.valueOf(idworkgroups.get(i))));

		return metoda(nameValuePairs, "get_tickets");
	}
private JSONArray metoda(ArrayList<NameValuePair> nameValuePairs, String uri) {
		String result = "";
		InputStream is = null;
		try {

			HttpPost httppost = new HttpPost(server + uri);
			httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
			HttpResponse response = httpclient.execute(httppost);
			HttpEntity entity = response.getEntity();
			is = entity.getContent();
		} catch (Exception e) {
			Log.e("log_tag", "Error in http connection " + e.toString());
		}
		// convert response to string
		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(
					is, "iso-8859-1"), 8);
			StringBuilder sb = new StringBuilder();
			String line = null;
			while ((line = reader.readLine()) != null) {
				sb.append(line + "\n");
			}
			is.close();

			result = sb.toString();
		} catch (Exception e) {
			Log.e("log_tag", "Error converting result " + e.toString());
		}

		// parse json data
		try {
			JSONArray jArray = new JSONArray(result);
			return jArray;
		} catch (JSONException e) {
			Log.e("log_tag", "Error parsing data " + e.toString());
		}
		return null;
	}

Android vs iOS

Aplikacja została wykonana pod system Android. Nie ma przeszkód aby zaimplementować to rozwiązanie zarówno na platformę iOS. System iOS posiada sdk, w którym językiem jest obiektowe C. Dzięki zastosowaniu języka niższego poziomu niż jakim jest język java, aplikacje działają płynniej. W przypadku tej aplikacji nie było wymagane płynne działanie, lecz na pewno użytkownik chętniej sięgnąłby po aplikację, która płynnie odpowiada na jego zapytania. Programując na platformie Android ma się do dyspozycji symulator, który całkowicie odzwierciedla urządzenie. Jest to zatem bardzo potężne narzędzie, jednak wymaga to uruchomienie wirtualnej maszyny Darvik, która ma bardzo wysoki narzut przez co nie działa płynnie. Symulator iPhone czy też iPad na komputerach mac nie emuluje urządzenia, a kompiluje napisany kod pod system Mac OS dzięki czemu aplikacje potrafią funkcjonować płynniej na symulatorze niż rzeczywistym urządzeniu.

W systemie iOS istnieje również SQLite, z którego można korzystać podobnie jak w przypadku Android, lecz znajduje się jeszcze CoreData, która mapuje obiekty na relacyjną bazę danych, co jest bardzo przydatnym narzędziem, którego nie spotkałem w przypadku programowania na system Android.

Sdk do platformy iOS zawiera zdecydowanie lepszy program do projektowania interfejsu użytkownika, posiada bogatszą literaturę, a programowanie widoku tabeli wydaje się być zdecydowanie wygodniejsze.

Literatura

The Pragmatic Programmers, Hello, Android Introducing Google's Mobile Devolopment Platform.

http://developer.android.com/reference/android/content/SharedPreferences.html

2011/04/02 14:31
pl/dydaktyka/ztb/2011/projekty/tickets.txt · ostatnio zmienione: 2019/06/27 15:50 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0