Tutorial Android #15 – Integrando Bibliotecas de Terceiros (Twitter)

Padrão

Olá pessoal!

No tutorial de hoje, vamos ver como integrar uma biblioteca externa ao nosso aplicativo em Android. Através dela, vamos vincular uma conta do Twitter ao restaurante e poderemos obter os últimos tweets referentes aquele restaurante. Para o acesso ao Twitter, utilizaremos a biblioteca twitter4j, que nos fornece acesso completo aos recursos da rede social. No tutorial estou utilizando a versão 2.2.5 otimizada para Android (twitter4j-android-2.2.5).

Bom, então sem mais delongas, vamos começar a colocar a mão na massa.

O primeiro passo é adicionar a conta do Twitter ao nosso modelo de dados. Isso implica em modificar a classe de persistência GerenciadorRestaurantes. Comece alterando o método onCreate() para abrigar o novo campo no banco de dados:

@Override
public void onCreate(SQLiteDatabase db) {
	db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT," +
			" nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT, twitter TEXT);");
}

Além disso, vamos alterar a versão do Schema do banco, para que ele seja atualizado em versões anteriores:

private static final int VERSAO_SCHEMA = 2;

Precisamos também atualizar o nosso método onUpdate(), que antes não fazia nada. Ele será executado se o usuário possuir a versão antiga do banco. Nesse caso, iremos adicionar a nova coluna twitter a tabela restaurantes.

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
	db.execSQL("ALTER TABLE restaurantes ADD COLUMN twitter TEXT");
}

Para concluir as modificações nesta classe, vamos atualizar os método obterTodos(), obterPorId(), inserir() e atualizar(). Além disso, também vamos adicionar o método obterTwitter():

public void inserir(String nome, String endereco, String tipo, String anotacoes, String twitter) {
	ContentValues valores = new ContentValues();

	valores.put("nome", nome);
	valores.put("endereco", endereco);
	valores.put("tipo", tipo);
	valores.put("anotacoes", anotacoes);
	valores.put("twitter", twitter);

	getWritableDatabase().insert("restaurantes", "nome", valores);
}

public void atualizar(String id, String nome, String endereco, String tipo, String anotacoes, String twitter) {
	ContentValues valores = new ContentValues();
	String[] argumentos = {id};

	valores.put("nome", nome);
	valores.put("endereco", endereco);
	valores.put("tipo", tipo);
	valores.put("anotacoes", anotacoes);
	valores.put("twitter", twitter);

	getWritableDatabase().update("restaurantes", valores, "_id=?", argumentos);
}

public Cursor obterTodos(String ordenacao) {
	return getReadableDatabase().rawQuery("select _id, nome, endereco, tipo, " +
			"anotacoes, twitter FROM restaurantes ORDER BY " + ordenacao, null);
}

public String obterTwitter(Cursor c) {
	return c.getString(5);
}

public Cursor obterPorId(String id) {
	String[] argumentos = {id};

	return getReadableDatabase().rawQuery(
			"SELECT _id, nome, endereco, tipo, anotacoes, twitter " +
			"FROM restaurantes WHERE _id = ?", argumentos);
}

Pronto. Com relação aos dados do aplicativo, já estamos prontos. Vamos agora ajustar o formulário de detalhes, adicionando o campo Twitter em ambos. Lembre-se que, como temos duas versões deste formulário (res/layout e res/layout-land), precisaremos fazer a modificação em ambos. Primeiramente o formulário para modo retrato (res/layout/form_detalhes.xml):

<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:stretchColumns="1" >
    <TableRow>
        <TextView android:text="Nome:"/>
        <EditText android:id="@+id/nome"/>
    </TableRow>
    <TableRow>
    	<TextView android:text="Endereço:"/>
    	<EditText android:id="@+id/end"/>
    </TableRow>
    <TableRow>
    	<TextView android:text="Tipo:"/>
    	<RadioGroup android:id="@+id/tipos">
    		<RadioButton android:id="@+id/rodizio"
       			android:text="Rodízio"/>
    		<RadioButton android:id="@+id/fast_food"
        		android:text="Fast Food"/>
        	<RadioButton android:id="@+id/a_domicilio"
            	android:text="A Domicílio"/>
   		</RadioGroup>
	</TableRow>
    <EditText android:id="@+id/anotacoes"
    	android:singleLine="false"
    	android:gravity="top"
    	android:lines="2"
    	android:scrollHorizontally="false"
    	android:maxLines="2"
    	android:maxWidth="200sp"
    	android:hint="Anotações"/>
    <EditText android:id="@+id/twitter"
        android:hint="Conta do Twitter" />
    <Button android:id="@+id/salvar"
    	android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Salvar"/>
</TableLayout>

Basicamente, a única modificação é a adição do campo twitter e a remoção do TextView do campo anotações, exibido agora como o atributo hint. A mesma coisa fazemos no formulário do modo paisagem (res/layout-land/form_detalhes.xml):

<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:stretchColumns="1,3" >
    <TableRow>
        <TextView android:text="Nome:"/>
        <EditText android:id="@+id/nome"
            android:layout_span="3" />
    </TableRow>
    <TableRow>
    	<TextView android:text="Endereço:"/>
    	<EditText android:id="@+id/end"
    	    android:layout_span="3" />
    </TableRow>
    <TableRow>
    	<TextView android:text="Tipo:"/>
    	<RadioGroup android:id="@+id/tipos">
    		<RadioButton android:id="@+id/rodizio"
       			android:text="Rodízio"/>
    		<RadioButton android:id="@+id/fast_food"
        		android:text="Fast Food"/>
        	<RadioButton android:id="@+id/a_domicilio"
            	android:text="A Domicílio"/>
   		</RadioGroup>
   		<TextView android:text="Anotações:"/>
   		<LinearLayout
   		    android:layout_width="fill_parent"
   		    android:layout_height="fill_parent"
   		    android:orientation="vertical">
   		    <EditText android:id="@+id/anotacoes"
    			android:singleLine="false"
	    		android:gravity="top"
    			android:lines="4"
    			android:scrollHorizontally="false"
    			android:maxLines="4"
    			android:maxWidth="140sp"
    			android:layout_width="fill_parent"
    			android:layout_height="wrap_content"
    			android:hint="Anotações" />
   		    <EditText android:id="@+id/twitter"
   		        android:layout_width="fill_parent"
   		        android:layout_height="wrap_content"
   		        android:hint="Conta do Twitter"/>
   		    <Button android:id="@+id/salvar"
    			android:layout_width="fill_parent"
        		android:layout_height="wrap_content"
        		android:text="Salvar"/>
   		</LinearLayout>
	</TableRow>
</TableLayout>

Agora, iremos adicionar o atributo twitter na classe FormularioDetalhes.

EditText twitter = null;

Em seguida, no método onCreate(), obtemos o valor do formulário e o aplicamos ao atributo da classe:

twitter = (EditText) findViewById(R.id.twitter);

E no método carregar(), configuramos o texto do EditText:

twitter.setText(gerenciador.obterTwitter(c));

Finalmente, vamos modificar as chamadas aos métodos de persistência lá no onSave:

private OnClickListener onSave = new OnClickListener() {

	public void onClick(View arg0) {
		String tipo = null;

		switch (tipos.getCheckedRadioButtonId()) {
		case R.id.rodizio:
			tipo = "rodizio";
			break;
		case R.id.fast_food:
			tipo = "fast_food";
			break;
		case R.id.a_domicilio:
			tipo = "a_domicilio";
			break;
		}

		if (idRestaurante == null) {
			gerenciador.inserir(nome.getText().toString(),
					endereco.getText().toString(),
					tipo, anotacoes.getText().toString(),
					twitter.getText().toString());
		} else {
			gerenciador.atualizar(idRestaurante,
					nome.getText().toString(),
					endereco.getText().toString(),
					tipo, anotacoes.getText().toString(),
					twitter.getText().toString());
		}

		finish();
	}
};

Acompanharam? O próximo passo é adicionar a opção Twitter ao menu. Crie o novo arquivo opcao_detalhes.xml e o salve em res/menu. O ícone tem pra download junto com o projeto lá no final do post.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
	<item android:id="@+id/twitter"
	    android:title="Timeline Twitter"
	    android:icon="@drawable/twitter"/>
</menu>

Em seguida, adicione o método onCreateOptionsMenu() a classe FormularioDetalhes.

@Override
public boolean onCreateOptionsMenu(Menu menu) {
	new MenuInflater(this).inflate(R.menu.opcao_detalhes, menu);

	return super.onCreateOptionsMenu(menu);
};

Como vamos utilizar a conexão de rede do celular com a Internet, precisamos verificar se essa conexão está disponível. Vamos então criar os métodos redeDisponivel() e o método onOptionsItemSelected() lá na classe FormularioDetalhes:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
	if (item.getItemId() == R.id.twitter) {
		if (redeDisponivel()) {
			Intent i = new Intent(this, TwitterActivity.class);
			i.putExtra(TwitterActivity.PERFIL, twitter.getText().toString());
			startActivity(i);
		} else {
			Toast.makeText(this, "Conexão com a Internet indisponível", Toast.LENGTH_LONG).show();
		}

		return true;
	}

	return super.onOptionsItemSelected(item);
}

private boolean redeDisponivel() {
	ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
	NetworkInfo info = cm.getActiveNetworkInfo();

	return (info != null);
}

Ao inserir estes dois métodos, o Eclipse vai chiar pela ausência da classe TwitterActivity. Calma! Já já resolveremos isso. Também precisaremos adicionar ao AndroidManifest.xml que nosso aplicativo precisa de permissão para acessar a rede. Assim, adicione as duas linhas seguintes ao arquivo, antes do nó application.

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Agora vamos começar a mexer com a obtenção dos tweets propriamente ditos. Para adicionar a biblioteca (dentro do arquivo Zip baixado, vamos adicionar o arquivo twitter4j-core-android-2.2.5.jar que se encontra dentro da pasta lib), clique com o botão direito sobre o projeto, selecione Properties, clique em Java Build Path, aba Libraries e clique no botão Add External JARs…. Localize o arquivo, e confirme.

Crie uma nova classe no pacote net.rafaeltoledo.restaurante chamada TwitterActivity estendendo ListActivity. Em seguida, adicione o mapeamento desta Activity no AndroidManifest.xml:

<activity android:name=".TwitterActivity">
</activity>

Agora, crie uma classe interna a classe TwitterActivity chamada TarefaTwitter:

private static class TarefaTwitter extends AsyncTask<String, Void, List<Status>> {
	private Twitter t = new TwitterFactory().getInstance();
	private Exception ex = null;
	private TwitterActivity activity = null;

	TarefaTwitter(TwitterActivity activity) {
		anexar(activity);
	}

	void anexar(TwitterActivity activity) {
		this.activity = activity;
	}

	void desanexar() {
		activity = null;
	}

	@Override
	protected List<twitter4j.Status> doInBackground(String... params) {
		List<twitter4j.Status> resultado = null;

		try {
			resultado = t.getUserTimeline(params[0]);
		} catch (Exception ex) {
			this.ex = ex;
		}

		return resultado;
	}

	@Override
	public void onPostExecute(List<twitter4j.Status> result) {
		if (ex == null) {
			activity.atribuirTweets(result);
		} else {
			Log.e("ListaRestaurantes", "Erro manipulando timeline twitter", ex);
			activity.atirarErro(ex);
		}
	}
}

Esta classe é a responsável por carregar os tweets da conta selecionada e enviá-los para a Activity (carregamento na linha 43 e atribuição para a Activity na linha 54). Sempre lembrando de corrigir os imports com o Ctrl + Shift + O 😉

Prosseguindo, vamos implementar os métodos da classe TwitterActivity referenciados pela classe TarefaTwitter. Primeiramente, vamos com o atirarErro():

private void atirarErro(Throwable t) {
	Builder builder = new Builder(this);
	builder.setTitle("Erro!").setMessage(t.toString()).setPositiveButton("OK", null).show();
}

Por fim, vamos fazer com que os tweets obtidos sejam exibidos. Dentro da TwitterActivity, vamos criar a classe AdaptadorTweets que será responsável por adaptar os nossos tweets para a exibição.

private class AdaptadorTweets extends BaseAdapter {
	List<Status> status = null;

	AdaptadorTweets(List<Status> status) {
		super();
		this.status = status;
	}

	public int getCount() {
		return status.size();
	}

	public Object getItem(int position) {
		return status.get(position);
	}

	public long getItemId(int position) {
		return position;
	}

	public View getView(int position, View convertView, ViewGroup parent) {
		View linha = convertView;

		if (linha == null) {
			LayoutInflater inflater = getLayoutInflater();
			linha = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
		}

		Status item = (Status) getItem(position);
		((TextView) linha).setText(item.getText());

		return linha;
	}
}

Com este adaptador, vamos exibir os tweets em uma lista comum (android.R.layout.simple_list_item_1). Para gerenciar as instâncias da TarefaTwitter e da lista de tweets, vamos criar uma classe interna StatusInstance:

private static class StatusInstancia {
	List<Status> tweets = null;
	TarefaTwitter tarefa = null;
}

Em seguida adicionamos um objeto desta classe como atributo da classe TwitterActivity:

private StatusInstancia status = null;

E implemente os métodos onCreate(), onRetainNonConfigurationInstance() e atribuirTweets().

@Override
public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);

	status = (StatusInstancia) getLastNonConfigurationInstance();

	if (status == null) {
		status = new StatusInstancia();
		status.tarefa = new TarefaTwitter(this);
		status.tarefa.execute(getIntent().getStringExtra(PERFIL));
	} else {
		if (status.tarefa != null) {
			status.tarefa.anexar(this);
		}

		if (status.tweets != null) {
			atribuirTweets(status.tweets);
		}
	}
}

@Override
public Object onRetainNonConfigurationInstance() {
	if (status.tarefa != null) {
		status.tarefa.desanexar();
	}
	return status;
}

private void atribuirTweets(List<Status> tweets) {
	status.tweets = tweets;
	setListAdapter(new AdaptadorTweets(tweets));
}

E no final, vamos criar o atributo PERFIL na classe:

public static final String PERFIL = "net.rafaeltoledo.PERFIL";

E prontinho! Cadastre um restaurante e adicione uma conta de Twitter pra ser feliz! 😀

Perdeu alguma parte? Não conseguiu acompanhar? Ou só tá com preguiça de digitar tudo? Só baixar aqui.

Até breve!!!

15 comentários sobre “Tutorial Android #15 – Integrando Bibliotecas de Terceiros (Twitter)

  1. Ana Paula

    Oi Rafael,

    Parabéns pelo post. A integração com o twitter pelo twitter4j tem poucos tutoriais na net. O seu ajuda muito quem está começando!

    Li mais de uma vez o seu tutorial e implementei conforme você indicou porém apresentaram-se os erros:
    NoClassDefFoundError: twitter4j.TwitterFactory
    com acusações de erros nas linhas “status.tarefa = new TarefaTwitter(this);” e “private Twitter t = twitterFactory.getInstance();”.

    Executei seu projeto e deu o mesmo erro.

    A execução foi feita usando um emulador com a versão 2.2 e um celular com android 2.3.7.

    Sabe como poderia me ajudar?

    • Olá Ana Paula! Tal problema deve-se pelo fato do aplicativo não estar encontrando a biblioteca. No meu projeto que está para download, possivelmente não vai rodar pois a referência aos caminhos dos arquivos feita pelo Eclipse é estática, ou seja, ele vai estar referenciando os caminhos no meu computador (a ideia de subir os projetos para download é mais para referência dos arquivos-fonte). Talvez esse link http://stackoverflow.com/questions/2316445/how-to-use-and-package-a-jar-file-with-my-android-app do Stack Overflow seja a resposta à sua dúvida. Você precisa exportar o jar junto com o projeto, lá em Java Build Path -> Order and Export. Caso ainda esteja com problemas, só avisar!

  2. Ana Paula

    Fica a sugestão para mencionar o cadastro da aplicação no twitter e adicionar como configurar o twitter4j. 🙂

    • Ah sim, com certeza. Nesse post a abordagem foi bem superficial porque o objetivo era como utilizar uma biblioteca externa em um app. Acabei descobrindo a Twitter4J sem querer e resolvi utilizar. Mas pode deixar que já vou anotar a sugestão pra fazer um post exclusivo pra esse assunto!

      Obrigado pela visita e pela sugestão!

  3. Thiago Pacheco

    Ótimo tutorial, mas estou tentando de exibir os twitters dentro de uma listview que está em um layout e não e não estou conseguindo, me dá uma dica.

    Para melhorar mais ainda uma sugestão seria incluir um Progress Dialog informando que esta carregando os posts, incluir um (puxe para atualizar), exibir a data das postagens e um (android:autoLink=”web”) para abrir os links.

    Isso são apenas sugestões, caso você for da continuidade no projeto.

    Obrigado pelos tutorias e sucesso.

    • Olá Thiago!

      Vou arrumar um tempinho pra dar uma pesquisada nessa sua dúvida e possivelmente fazer alguns posts com essas suas sugestões… essa semana está realmente corrido (tanto que fiquei devendo post). De qualquer forma, muito obrigado pela visita e pelas sugestões!

      Sucesso!! 😀

Deixe uma resposta