quinta-feira, 30 de junho de 2011

Android + RESTful + JSON

Olá pessoal,

No post anterior mostrei como criar um Web Service simples com o Jersey. Através dele podemos retornamos um arquivo JSON baseado no padrão RESTful.

Nesse pequeno POST, vou mostrar como ler os dados desse WS com o Android. O REST/JSON está tão forte que a Google já colocou nativamente no Android uma API para leitura desse tipo de arquivo. Então, dentro do seu projeto basta colocar os métodos abaixo:


private String toString(InputStream is)
throws IOException{

byte[] bytes = new byte[1024];
ByteArrayOutputStream baos =
new ByteArrayOutputStream();
int lidos;
while ((lidos = is.read(bytes)) > 0){
baos.write(bytes, 0, lidos);
}
return new String(baos.toByteArray());
}


Esse primeiro método é uma "receita de bolo". Ele será pelo método getRESTFileContent(String) (a seguir) e servirá para ler byte-a-byte (na verdade de 1024 em 1024 bytes) o conteúdo do arquivo JSON que é retornado pelo servidor, retornando-o em forma de String.

public String getRESTFileContent(String url) {
HttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet(url);

try {
HttpResponse response =
httpclient.execute(httpget);

HttpEntity entity = response.getEntity();

if (entity != null) {
InputStream instream = entity.getContent();
String result = toString(instream);

instream.close();
return result;
}
} catch (Exception e) {
Log.e("NGVL", "Falha ao acessar Web service", e);
}
return null;
}

O método acima, utiliza o HttpClient (da Apache, que vem nativamente no Android) para estabelecer a conexão com o servidor, abrir o fluxo de leitura (InpuStream) e retornar o conteúdo do arquivo JSON em forma de String (feito pelo método toString(InputStream)).

private void lerTodasPessoas() {
String result = getRESTFileContent(
"http://10.0.2.2:8080/BlogWS_REST/rest/pessoas/");
if (result == null){
Log.e("NGVL", "Falha ao acessar WS");
return;
}

try {
JSONObject json = new JSONObject(result);
JSONArray pessoasArray =
json.getJSONArray("pessoa");

JSONObject pessoaJson;

for (int i = 0; i < pessoasArray.length(); i++) {
pessoaJson = new JSONObject(
pessoasArray.getString(i));

Log.i("NGVL",
"------------------------");
Log.i("NGVL",
"id="+ pessoaJson.getInt("id"));
Log.i("NGVL",
"nome="+ pessoaJson.getString("nome"));
}
} catch (JSONException e) {
Log.e("NGVL", "Erro no parsing do JSON", e);
}
}

private void lerUmaPessoa() {
// Observe o ID da pessoa no final da URL
String result = getRESTFileContent(
"http://10.0.2.2:8080/BlogWS_REST/rest/pessoas/1");
if (result == null){
Log.e("NGVL", "Falha ao acessar WS");
return;
}

try {
JSONObject pessoaJson = new JSONObject(result);

Log.i("NGVL",
"id="+ pessoaJson.getInt("id"));
Log.i("NGVL",
"nome="+ pessoaJson.getString("nome"));

} catch (JSONException e) {
Log.e("NGVL", "Erro ao fazer parsing do JSON", e);
}
}

Este é o método que deverá ser chamado diretamente e utilizará os demais. Inicialmente ele pega o conteúdo do arquivo JSON informando a URL do mesmo. Notem que foi usado o IP 10.0.2.2 estabelecido pelo Android para acessarmos um serviço da máquina local.
Em seguida, criamos o objeto JSON e a partir dele obtemos o array de pessoas. Esse array contém todos os objetos JSON do arquivo. Em seguida, iteramos sobre este array atribuindo à variável pessoaJson. E com essa variável pegamos a propriedade de cada objeto retornado.
Editado em 06/11/2011
Agora vou mostrar como passar um JSON para o nosso servidor inserir uma nova Pessoa.

private String inserirPessoa() {
try {
URL url = new URL(
"http://10.0.2.2:8080/BlogWS_REST/rest/pessoas/");
HttpURLConnection conexao =
(HttpURLConnection)url.openConnection();

conexao.setRequestMethod("POST");
conexao.addRequestProperty(
"Content-type", "application/json");

conexao.setDoOutput(true);

conexao.connect();

OutputStream os = conexao.getOutputStream();
os.write("{\"id\":\"1\",\"nome\":\"Nelson\",\"outro\":\"info1\"}".getBytes());
os.flush();

InputStream is = conexao.getInputStream();
return toString(is);

} catch (Exception e) {
e.printStackTrace();
return "ERRO!";
}
}

Este método abre a conexão com o servidor, mas desta vez para fazer um POST ao invés de um GET. Ele passará um objeto JSON que representa um objeto Pessoa no servidor através de um OutputStream. Aqui colocamos um JSON hard-coded, mas você poderia utilizar a bibilioteca GSON do Google para converter seus objetos em JSON.
[/Editado]

Dois detalhes: 1) toda comunicação deve ser feita na Thread separada da UI. Logo chame o método acima dentro de uma outra Thread ou use a abordagem da AsyncTask que eu mostrei nesse post. 2) Você deve adicionar a permissão android.permission.INTERNET no seu AndroidManifest.XML conforme abaixo:
<uses-permission android:name="android.permission.INTERNET"/>


Qualquer dúvida deixem seus comentários.

4br4ç05,
nglauber

52 comentários:

Thiago disse...

Boa tarde...
Muito bom o tutorial... principalmente a 1 parte onde mostra o passo a passo da criacao do servico utilizando json...
Teria como postar o projeto Android para download... pois ficou meio vago a estrutura da chamada para consumir o WS...

obrigado....

Nelson Glauber disse...

Oi Thiago,

Segue abaixo o link com um exemplo do uso do código do post

https://sites.google.com/site/nglaubervasc/ExemploRESTClient.zip

4br4ç05,
nglauber

Thiago disse...

Obrigado Nelson...

Muito bom o exemplo...
Estou com o seguinte problema..se vc puder me ajudar ficarei grato.. estou querendo passar para o meu ws(servidor), alguns parametros para a consulta,, porem quero encapsular estes parametros dentro de um objeto... devo transformar este objeto numa string Json ou utilizar um hashmap,,, vc utilizou este tipo de consulta no Android...

Obrigado

Nelson Glauber disse...

Oi Thiago,

No cliente você deve adicionar o parâmetro a sua requisição. Abaixo eu substitui o GET por POST.

HttpPost httpPost = new HttpPost(url);
List params =
new ArrayList();
params.add(
new BasicNameValuePair("nome", "Glauber"));
UrlEncodedFormEntity ent =
new UrlEncodedFormEntity(params,HTTP.UTF_8);
httpPost.setEntity(ent);

Já no servidor, no método que você adicionar parâmetros, utiliza a notação @FormParam como abaixo:

public List obterPessoas(@FormParam("nome") String nome) {
...
}

4br4ç05,
nglauber

Jhonathan Brandão disse...

Esse exemplo que você passou o link para download está completo?

Muito bom para que está iniciando, assim como eu.

Jhonathan Brandão disse...

Deu certo! Antes não mostra o texto com o resultado do WS. Depois que eu coloquei um texto inicial para ver se mudava alguma coisa, passou a aparecer. Não sei explicar o por que, mas vou continuar estudando. Muito obrigado por disponibilizar seu conhecimento.

Rafael disse...

caro - eu tenho um restful em java, que retorna dois campos, codigo e descricao. Até então, consigo parsear sem problema.
Porém, precisei incluir um novo campo, chamado codigoNCM. O que acontece é que esse campo pode ser nulo ou estar preenchido. No parser JSON, ele está retornando:

"codigo":"01";"descricao":"teste"
quando o campo está nulo, e
"codigo":"01";"descricao":"teste";"codigoNCM": "123"

quando não está nulo. Como pode ver, ele só faz a inclusão do campo quando ele nao está nulo. Como eu trato essa situação?

Nelson Glauber disse...

Olá Rafael,

É só utilizar o método isNull do objeto JSONObject.

if (!objetoJson.isNull("codigoNCM"))

4br4ç05,
nglauber

Rafael disse...

Nelson - funcionou bem a sua sugestão, tanto que eu implementei ela em tudo. Porém, depois, encontrei algo que pode ser usado também.

No web.xml, inserir:


com.sun.jersey.api.json.POJOMappingFeature
true


Isso faz com que os objetos sejam mapeados de acordo com o POJO, o que no meu caso fez todo mundo retornar nulo. Para ajudar, isso também permite que os atributos dos POJOs definidos como INTEGER ou DOUBLE, venham nesse formato, ao invés de virem como String...

Mas, infelizmente, surge um outro problema. Meu JSOn era:

"obj":{"codigo":"01";"descricao":"teste"}

e agora, sumiu a definição do "obj". Ele fica apenas

{"codigo":"01";"descricao":"teste"}

Existe alguma configuração que force o RESTful em Java sempre criar o nome do objeto?

Leonardo Saganski disse...

Olá Nelson,
sou novo no mundo Android e estou experimentando troca de dados por JSON. Seu post me ensinou muito e seu blog ganhou mais um fã hehehe.

Minha dúvida é a seguinte :
1 ) Fiz um WCF Rest ( .net ) que retorna uma lista de produtos no formato JSON. Entre os campos dessa classe produtos tenho um campo imagem, que contém a foto do mesmo. Esta imagem foi enviada como um array de bytes :

{"GetItensResult":
[{"Descricao":"",
"ID":32,
"Imagem":[255,216,255,224,0,16,74,70,73,70....

2 ) Consegui recuperar o JSON no Android, porém no momento de resgatar
os campos, não sei como resgatar a imagem :

myit.Nome = itJson.getString("Nome");
...
myit.Imagem = itJson. ???? ("Imagem");

pois não tem um Getbyte[] :-P
sendo que a propriedade Imagem do objeto myit ja é do tipo byte[].

Agradeço antecipadamente.

Nelson Glauber disse...

Oi Leonardo,

Nesse caso, você deve pegar essa String e usar o método split para quebra-la em um array de Strings contendo os bytes.

String bytesStr =
json.getString("Imagem");
String s[] = tar.split("\\,");
byte[] b = new byte[s.length];
for (int i=0; i < s.length; i++){
b[i] = Byte.valueOf(s[i]);
}

E com o array de bytes "b", você cria a imagem e seta, em uma ImageView, por exemplo.

Bitmap bmp = BitmapFactoty.decodeByteArray(
b, 0, b.length);
imageView.setImageBitmap(bmp);

4br4ç05,
nglauber

Joshua Maia Rodrigues disse...

Excelente Blog. Resolvi um problema de comunicação via Post pelo Android para uma APP Web que utiliza RESTful. Já li inúmeros posts do Blog do Nelson, e sempre tem algo a acrescentar. Parabêns e Abraços.

karlon disse...

Olá Nelson, eu também gostei muito do Tutorial. Mas ainda sou novo nessa área de WebService, até então eu utilizava axis2, por isso não entendi ainda como enviar parâmetros para o webservice. Você explicou para o Thiago que

"No cliente você deve adicionar o parâmetro a sua requisição. Abaixo eu substitui o GET por POST."

Só que eu não entendi, onde eu coloco essas linhas? onde eu substituo get por POST? em qual método faço isso?

Desde já agradeço e o parabenizo pelo ótimo tutorial.

Nelson Glauber disse...

Oi karlon,

Não entendi tua dúvida... Quando eu expliquei pro Thiago, já coloquei o exemplo de como se faz. Qualquer dúvida, me fala.

4br4ç05,
nglauber

karlon disse...

Eu não entendi onde coloco essas linhas:

HttpPost httpPost = new HttpPost(url);
List params =
new ArrayList();
params.add(
new BasicNameValuePair("nome", "Glauber"));
UrlEncodedFormEntity ent =
new UrlEncodedFormEntity(params,HTTP.UTF_8);
httpPost.setEntity(ent);



Seria no Método getRESTFileContent ?

Nelson Glauber disse...

Olá karlon,

Exatamente. O código deve ser substituído nesse método.

Apenas um conselho (que se fosse bom, não se dava): antes postar dúvidas nos blogs/foruns por aí, pesquisa e testa antes.
Nós que trabalhamos com TI temos como característica intrínseca pesquisar coisas. Isso porque estão sempre lançando novas ferramentas/APIs de desenvolvimento. Então, se você pesquisar mais, e chegar com a dúvida mais "mastigadinha" vai poupar o seu tempo e de quem responde (como eu).

4br4ç05,
nglauber

karlon disse...

Muito obrigado, agradeço muito a atenção. Nessas últimas semanas me deparei com a necessidade de retornar do meu webservice uma coleção de dados, e não mais dados primitivos que eu conseguia retornar com tanta facilidade em axis2. Por isso me vi obrigado a utilizar o padrão REST e o protocolo JSON, é bem diferente do que eu havia aprendido anteriormente e mesmo pesquisando muito eu não estava conseguindo entender os exemplos.
Muito obrigado Glauber e parabéns pela disposição em ajudar. Valew!

RH disse...

Nelson quando eu executo a aplicação no meu tablet ele me da o erro dizendo "Falha ao acessar Web service", e na URL "http://meu ip/BlogWS_REST/rest/pessoas/" e nao funciona, existe outra coisa a fazer???

Nelson Glauber disse...

Oi RH,

O Tablet e sua máquina estão na mesma rede Wi-Fi? Será que o Firewall não está bloqueando? Você colocou a porta correta?

4br4ç05,
nglauber

Anônimo disse...

Parabéns pela didática do exemplo, já tinha lido muita coisa mais o seu post foi o melhor. Obrigado.

Wellington Sousa disse...

Olá Nelson me ajuda!!!
Estou começando com Android agora e estou com dificuldades em receber dados com vários registros do webservice, usando o
KaSOAP consegui retornar quando só tem um registro. Meu webservice está em .NET retornando um Json String, Tentei usar o seu exemplo, mas dar erro de Parse.

Vi que se eu entrar no código fonte do browse, os dados do webservice está em XML e tag STRING, pode ser isso que não estou conseguindo ler os dados? Como limpar isso ao receber os dados pelo Android?

Wellington Sousa disse...

Olá Nelson, parabéns pelo Blog, muito bom!!

Preciso da sua ajuda, tenho um webservice em .NET retornando um Json String. Pelo código fonte do Brownse, vi que o retorno apesar do formato Json está com cabeçalho XML e tag "'. Tentei usar seu código com RESTFUL e dar erro de Parse o que devo fazer?

Nelson Glauber disse...

Oi Wellington,

Fiquei meio confuso com sua dúvida... Você falou que está usando um WebService .net e está usando kSOAP? Mas esse post é sobre REST que é diferente de SOAP...

Dá uma olhada nesse post: http://nglauber.blogspot.com.br/2011/02/web-services-no-android.html se você estiver usando mesmo SOAP.

Um detalhe é que já que está usando .net, você terá que setar a propriedade dotNet do objeto SoapSerializationEnvelope para true.

4br4ç05,
nglauber

Wellington Sousa disse...

Olá Nelson Na verdade é o seguinte, já procurei exemplos com ksoap e todos mostram como trazer apenas uma informação do banco e não consegui encontrar nenhum exemplo trazendo uma listagem de dados para um listview com ksoap. Agora estou tentando usar o seu código com RESTFUL e também não estou conseguindo, porque dá um erro de Parse...Então a minha dúvida é a seguinte, verifiquei que no meu webservice .NET apesar das informações terem um formato de Json, se eu entrar no cpodigo fonte ele mostra um cabeçalho XML e uma String, será que o erro de Parsing é por isso? Você saberia me ajudar como resolver isso?

Desde já agradeço.

Wellington Sousa disse...

Nelson, boa noite no seu post: http://nglauber.blogspot.com.br/2011/02/web-services-no-android.html, que vc me sugeriu, vi que parece que com esse código é possível trazer uma listagem de dados o que resolve meu problem,más o meu webservice retorna dados em DataTable e DataSet, como converter isso no android para funcionar?

Desde já agradeço...

Nelson Glauber disse...

Oi Wellington,

Isso é um erro comum de quem usa dotNet e suas "facilidades". Ao invés de retornar um DataSet, retorna uma lista de objetos.
Achei esses links que podem te ajudar:
http://stackoverflow.com/questions/3155153/c-sharp-net-web-service-and-returning-list-of-objects-that-have-children-with-a
http://www.dotnetcurry.com/ShowArticle.aspx?ID=320
http://ryanfarley.com/blog/archive/2004/05/26/737.aspx

4br4ç05,
nglauber

Unknown disse...

opa nelson, td bom?
primeiro agradecer pelo seu post, me ajudou bastante nessa ligação WebServices + Android. Excelente post!
E eu detectei um erro, na vdd, não sei se é um erro atual, mas eu constatei.
É no método lerTodasPessoas(), vc está transformando o result em JSONObject, pra depois virar JSONArray e voltando a ser JSONObject. E ele dá erro de conversão do result pro JSONObject. Então apenas tirei esta linha e coloquei:
JSONArray pessoasArray = new JSONArray(result);
E me retornou corretamente =]

Té mais,

Bruno.

Leonel Araújo disse...

Opa Glauber blz, cara grande post funcionou blz para consumir um Array de JSON de um simples arquivo no Dropbox. Mais estou com um pequeno problema... o texto quando acentuado aparece no lugar da acentuação caracteres especiais. Exemplo: "você", "voc?" mais ou menos isso... já passou por essa situação ? O código implementado é o mesmo desse post a qual comento...

Nelson Glauber disse...

Oi Leonel,

Pode ser o encoding do arquivo esteja incompatível com o que você está lendo.
No método toString(InputStream is), tenta substituir o retorno por new String(baos.toByteArray(), "UTF-8") ou para o encoding que o arquivo esteja.

4br4ç05,
nglauber

Unknown disse...

Glauber, blz!

Me tira uma dúvida, eu estou tentando adicionar parâmetro dentro do GET e estou recebendo um erro http 422

fiz dessa forma...

DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet get = new HttpGet(
"http://safe-sea-4024.ppooiherokuapp.com/crimes/mobilelist");

get.setHeader("Accept", "application/json");
get.setHeader("Content-type", "application/json");

get.getParams()
.setParameter("token",
"0V1AYFK12SeCZHYgXbNMew==$tRqPNplipDwtbD0vxWv6GPJIT6Yk5abwca3IJa6JhMs=");

HttpResponse httpResponse;
try {
httpResponse = httpClient.execute(get);
String jsonDeResposta = EntityUtils.toString(httpResponse
.getEntity());

System.out.println();

} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

Tentei da forma que vocÊ falou no post e recebo um HTTP 404

List params = new ArrayList();
params.add(new BasicNameValuePair("token",
"0V1AYFK12SeCZHYgXbNMew==$tRqPNpli"));
UrlEncodedFormEntity ent;

tu sabe dizer o por que de eu não está recebendo o arquivo JSON?

Nelson Glauber disse...

Oi Unknown :)

Quando vamos fazer uma requisição HTTP via GET, os parâmetro vão na própria URL.
Ex.: http://a.com/pagina.php?nome=Nelson&sobrenome=Leal&idade=29

Tenta usar isso aqui pra montar sua URL

URIBuilder builder = new URIBuilder();
builder.setScheme("http").setHost("safe-sea-4024.ppooiherokuapp.com").setPath("/crimes/mobilelist")
.setParameter("token", "0V1AYFK12SeCZHYgXbNMew==$tRqPNplipDwtbD0vxWv6GPJIT6Yk5abwca3IJa6JhMs=");
URI uri = builder.build();
HttpGet httpget = new HttpGet(uri);

4br4ç05,
nglauber

José Rodrigues disse...

Boa tarde, eu gostaria de saber como eu poderia enviar uma imagem e resgatar ela no meu aplicativo android. poderia me ajudar?

Nelson Glauber disse...

Oi José,

Tenta fazer o upload do arquivo.
http://nglauber.blogspot.com.br/2012/10/upload-de-arquivos-no-android.html

4br4ç05,
nglauber

Unknown disse...

muito bom o post. Me ajudou bastante. rsrs

Unknown disse...

Muito bom este post. Me ajudou !!! Bastante... rs

Luiz disse...

Nelson boa tarde!
estou tentando fazer a comunicação da minha aplicação android com um webservice e ele está mandando a seguinte mensagem de erro: java.lang.ClassCastException: org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.

A mensagem ocorre na 2ª linha

URL url = new URL(urlInserirUsuario);
HttpsURLConnection conexao = (HttpsURLConnection) url.openConnection();

tem alguma idéia do que pode estar acontecendo?

Nelson Glauber disse...

oi Luiz,

Se sua url é http normal vc deve usar HttpUrlConnection e não HttpsUrlConnection (note o "s" depois do http)

4br4ç05,
nglauber

Luiz disse...

Oi Glauber, Boa noite!
Após os ajustes necessários, o projeto funcionou perfeitamente, muito obrigado!

Jow Lª0Kkø disse...

o meu quando a lista possui apenas um elemento nao funciona, o metodo jsonObj.getJSONArray retorna uma exepction, pode me ajudar?
obrigado

Nelson Glauber disse...

Oi Jow,

Debuga o código e vê como está vindo a variável result. Provavelmente ela não está retornando um JSONArray válido.

4br4ç05,
nglauber

André disse...

Olá, tudo bom ?
o meu projeto funciona no emulador, mas não está funcionando no android, vc sabe como me ajudar ?
obrigado.

Nelson Glauber disse...

Olá André,

Se você estiver usando um aparelho real e o servidor estiver no seu computador você deve checar se ele o dispositivo Android estão na mesma rede wi-fi. Depois veja se o IP que está na app Android é o mesmo IP da sua máquina na rede local.
Por último, checa se o seu firewall não está bloqueando o acesso.

4br4ç05,
nglauber

Unknown disse...

Boa noite..
Eu estou seguindo o seu tutorial(muito bom, parabéns) e só consigo acessar o web service no emulador. No smartphone não consigo acessar o web service porque a firewall está-me a bloquear o acesso.

Como resolvo essa situacao?

Obrigado

Nelson Glauber disse...

Oi Rui,

Você terá que liberar no seu firewall. Lembre-se que o computador e o smartphone deve estar na mesma rede wifi. Ou seja, pela rede móvel (3G ou 4G) você não conseguirá acessar a máquina da sua rede local.

4br4ç05,
nglauber

Jhone Darts disse...

Gostaria de saber como testar a conexão do android cliente com a url especifica do server. Achei varios exemplos mas todos ele só mostram como verificar se o android tem acesso a internet o que nao me dá certeza que terei acesso a url que quero.

Nelson Glauber disse...

Oi Jhone,

O que você pode fazer é tentar acessar a URL, se o response code for diferente de 200 é que a resposta não foi bem sucedida...

URL url = new URL(
"http://sua.url.aqui");
HttpURLConnection conexao =
(HttpURLConnection)url.openConnection();
conexao.setRequestTimeout(10000);
conexao.connect();
if (conexao.getResponseCode() != 200){
// Deu algo errado...
}

A requisição pode dar timeout se o servidor estiver fora, nesse caso será levantada uma exceção que você deve tratar.


4br4ç05,
nglauber

Frederico Brigatte disse...

Belo material, Nelson. E se os dados viessem de um banco de dados, como que seria apenas uma listagem dos dados?

Nelson Glauber disse...

Oi Unknown,

Não faria nenhuma diferença pro Android. Para ele, isso é um arquivo JSON que está sendo lido. Como ele foi gerado (hardcoded ou com dados do banco) dá no mesmo.

4br4ç05,
nglauber

Blog do ueder disse...

Nelson, boa tarde seu post está incrível. Só que eu tenho uma dúvida, como eu faço para verificar se é um JSONObject ou um JSONArray ?

JSONObject = {"celular":{"imei":"111111111111"}}
JSONArray = {"celular":[{"imei":"111111111111"},{"imei":"000000000000000"}]}

Nelson Glauber disse...

Oi Ueder,

Se começa com { é JSONObject, se começa com [ é JSONArray :)

4br4ç05,
nglauber

Bruno Romualdo disse...

Ola Nelson, recentemente comprei seu livro sobre android e estou achando otimo muito bem explicado.
Entao queria tirar uma duvida sobre ele em questao do web service em php.
Ja baixei o seu exemplo e rodei no android studio para ver e estou conseguindo inserir(POST) e retornar(GET) mas nao estou conseguindo atualizar(PUT) os hoteis, coloquei um LOG.d() para mostrar o codigo HTTP de retorno e ele retorna o codigo 403, ja nao sei mais o que fazer, ja revisei todo o codigo do webservice em php mas esta tudo certo.
Agradeço desde já pela atenção e sucesso.

Nelson Glauber disse...

Oi Bruno,

Desculpa a demora em responder. Coloca essa dúvida lá no grupo de discussão do livro.
Mas aparentemente é um problema de acesso a arquivo. Checa se tem permissão de escrita na pasta.

4br4ç05,
nglauber