Swift Thread issues - ios

I have a function where I download a picture from internet and I put it inside a UIImage, it also adds a website to the picture (UIButton). The Urls are stored in my DB and I store them in an Array. Then I generate a random number to get an index and use it to download the picture. I run the code in my viewDidLoad and it shows the picture and if I click it, it opens the webpage. I am also using a timer to repeat this function every 5 seconds so I was expecting it to display a different picture and website.
Unfortunately it keeps displaying the same picture (webpage is changed) and once in a while it changes the picture, but in console this message is displayed "This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes."
What can I do to make this function work properly? I thank you all in advance. my code is the following
func descargaPicture(){
do {
let defaults = UserDefaults.standard
let miCIudad = defaults.object(forKey: "ciudad") as! String
let query = tblAd.filter(ciudad == miCIudad)
for infoAd in try conn.db!.prepare(query) {
//Guardar en arreglos
if(imagen.contains(infoAd[fotoAd]!)) {
print("ya existe")
} else {
imagen.append(infoAd[fotoAd]!)
}
if(web.contains(infoAd[webAd]!)){
} else {
web.append(infoAd[webAd]!)
}
} //Termina for
//-------------------------Descarga foto Internet-------------------------------
/*
Este proceso es similar a los anteriores, lo que hacemos es, después de obtener la imagen de nuestra BD, procedemos a hacer una petición para descargar la imagen del internet, así para poder después usarla como banner/Ad en determinadas Views, por lo que este bloque de código se encuentra presente en unas pocas clases de nuestro proyecto.
*/
let numero = imagen.count-1
if numero <= 0 {
print("El array esta vacio")
return
}
else {
var filtro = Int (arc4random_uniform(UInt32(numero)+1))
if imagen[filtro] != "" {
let picture = URL(string: self.imagen[filtro])!
let session = URLSession(configuration: .default)
let downloadPicTask = session.dataTask(with: picture) {(data, response, error) in
if let e = error {
print("Erro al descargar la imagen: \(e)")
}
else {
self.imagenAd.image = nil //-----------
if let res = response as? HTTPURLResponse {
print("Descargando foto, respuesta: \(res.statusCode)") //Da 200 como resp.
if let imageData = data {
let imagen = UIImage(data: imageData as Data)
self.imagenAd.image = imagen
self.webSite = self.web[filtro]
}
else {
print("No se puede obtener imagen")
}
}
else {
print("No se obtiene respuesta del servidor")
}
}
}
downloadPicTask.resume()
}
else {
print("Es NIL")
return
}
} //Cerramos el else definitivo
//--------------------/Descarga foto Internet ------------------------------
} //do
catch {
//Errores
}
}

The error says it all. You should do all UI related tasks on main thread.
Get hold of the main thread using
DispatchQueue.main.async {
//your UI Changes go here
}
And do all your UI Changes in this code block after getting the response.

Related

Create a folder after uploading and saving a document

I want after downloading a file that it is automatically put in "My iphone" with a folder created beforehand ( for example "CreateFolder")
Currently I have done this following code but it displays the choice to the user if they want to save it or whatever. What i don't want
My code :
let urlString = body;
let url = URL(string: urlString);
// Récupération du nom du fichier complet + que l'extension + que le nom
let fileName = String((url!.lastPathComponent)) as String;
let fileExt = String((url!.pathExtension)) as String
let fileNameWithoutExt : String = String(fileName.prefix(fileName.count - (fileExt.count+1)));
// Create destination URL
// Création du chemin (système) par défaut ( file:///var/mobile/Containers/Data/Application/6CD1C41B-8D24-466B-A32B-2EC26FE1E8C6/Documents/)
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)!;
var destinationFileUrl = documentsUrl.appendingPathComponent("\(fileName)");
// Vérification présence fichier si il existe on rajoute un (1) etc..
var counter = 0;
var newFileName : String!;
while FileManager().fileExists(atPath: destinationFileUrl.path) {
counter += 1;
newFileName = "\(fileNameWithoutExt)_\(counter).\(fileExt)";
destinationFileUrl = documentsUrl.appendingPathComponent(newFileName);
}
//Create URL to the source file you want to download
// Création pour le lancement du téléchargement
let fileURL = URL(string: urlString);
let sessionConfig = URLSessionConfiguration.default;
let session = URLSession(configuration: sessionConfig);
let request = URLRequest(url:fileURL!);
// Ici commence le téléchargement
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
}
do {
// Cette partie permet de copier le fichier stocké ( système Apple ) à l'endroit voulu
// avec le nouveau du fichier si déjà présent
try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl);
do {
//Show UIActivityViewController to save the downloaded file
// Partie qui permet d'afficher le "popup" pour savoir ce que l'on souhaite faire avec le document téléchargé ( envoyer, enregistrer etc...)
// Récupération de tous les fichiers dans le chemin système
let contents = try FileManager.default.contentsOfDirectory(at: documentsUrl, includingPropertiesForKeys: nil, options: .skipsHiddenFiles);
// La boucle permet de parcourir tous les fichiers stocké dans le chemin système d'Apple
// Arriver au dernier ( donc celui téléchargé )
for indexx in 0..<contents.count {
if contents[indexx].lastPathComponent == destinationFileUrl.lastPathComponent {
// Ceci met en fil d'attente les éléments et affiche le popup quand la boucle est terminée
DispatchQueue.main.async {
// Affiche le popup de ce que l'on souhaite faire
let activityViewController = UIActivityViewController(activityItems: [contents[indexx]], applicationActivities: nil);
self.present(activityViewController, animated: true, completion: nil);
}
}
}
}
catch (let err) {
print("error: \(err)");
}
} catch (let writeError) {
print("Error creating a file \(destinationFileUrl) : \(writeError)");
}
} else {
print("Error took place while downloading a file. Error description: \(error?.localizedDescription ?? "")");
}
}
task.resume();
This is what I would like :
Is it possible to create a folder and put the downloaded file without selecting the destination by the user?
Thanking you in advance
I found a solution for those who are looking to have their file To do this in your Info.plist file add the following 2 keys
Application Supports Itunes file sharing
Supports opening documents in place
With my code, you can comment out DispatchQueue.main.async (because useless now) and you will see your files in "In My Iphone"
Thank you very much to #matt
With your document open, click File > Save As.
Under Save As, select where you want to create your new folder.
In the Save As dialog box that opens, click New Folder.
Type the name of your new folder, and press Enter.
Click Save.

firebase authentication error in other languages

I am currently developing an app in Swift and I have linked firebase in order for users to signup and login. The app is supposed to be in Spanish but I can't find a way to translate the authentication errors to Spanish. Is there an option in the firebase console to enable other languages or in the plist. Any help is appreciated.
Here is an example, how you can make custom error descriptions for any kind of Error Type
import Foundation
import Firebase
extension AuthErrorCode {
var description: String? {
switch self {
case .emailAlreadyInUse:
return "Este correo ya está siendo usado por otro usuario"
case .userDisabled:
return "Este usuario ha sido deshabilitado"
case .operationNotAllowed:
return "Operación no permitida"
case .invalidEmail:
return "Correo electrónico no valido"
case .wrongPassword:
return "Contraseña incorrecta"
case .userNotFound:
return "No se encontró cuenta del usuario con el correo especificado"
case .networkError:
return "Promblema al intentar conectar al servidor"
case .weakPassword:
return "Contraseña muy debil o no válida"
case .missingEmail:
return "Hace falta registrar un correo electrónico"
case .internalError:
return "Error interno"
case .invalidCustomToken:
return "Token personalizado invalido"
case .tooManyRequests:
return "Ya se han enviado muchas solicitudes al servidor"
default:
return nil
}
}
}
extension FirestoreErrorCode {
var description: String? {
switch self {
case .cancelled:
return "Operación cancelada"
case .unknown:
return "Error desconocido"
case .invalidArgument:
return "Argumento no valido"
case .notFound:
return "No se encotró el documento"
case .alreadyExists:
return "El documento que se pretende crear, ya existe"
case .permissionDenied:
return "No tienes permisos para realizar esta operación"
case .aborted:
return "Operación abortada"
case .outOfRange:
return "Rango invalido"
case .unimplemented:
return "Esta operación no ha sido implementada o no es soportada aún"
case .internal:
return "Error interno"
case .unavailable:
return "Por el momento el servicio no está disponible, intenta más tarde"
case .unauthenticated:
return "Usuario no autenticado"
default:
return nil
}
} }
extension StorageErrorCode {
var description: String? {
switch self {
case .unknown:
return "Error desconocido"
case .quotaExceeded:
return "El espacio para guardar archivos ha sido sobrepasado"
case .unauthenticated:
return "Usuario no autenticado"
case .unauthorized:
return "Usuario no autorizado para realizar esta operación"
case .retryLimitExceeded:
return "Tiempo de espera excedido. Favor de intentar de nuevo"
case .downloadSizeExceeded:
return "El tamaño de descarga excede el espacio en memoria"
case .cancelled:
return "Operación cancelada"
default:
return nil
}
} }
public extension Error {
var localizedDescription: String {
let error = self as NSError
if error.domain == AuthErrorDomain {
if let code = AuthErrorCode(rawValue: error.code) {
if let errorString = code.description {
return errorString
}
}
}else if error.domain == FirestoreErrorDomain {
if let code = FirestoreErrorCode(rawValue: error.code) {
if let errorString = code.description {
return errorString
}
}
}else if error.domain == StorageErrorDomain {
if let code = StorageErrorCode(rawValue: error.code) {
if let errorString = code.description {
return errorString
}
}
}
return error.localizedDescription
} }
Firebase errors are not localized. You could request a feature for this via Firebase support.
What you could do instead is create your own localized messages based on the error codes provided. This is what FirebaseUI does:
FirebaseUI-ios
FirebaseUI-web
Or you could just simply use FirebaseUI-ios. It would save you a lot of time and effort.

Why IsolatingStorageSettings is empty when i restart my APP ? (WP8)

I use the IsolatingStorageSetting to store a collection of objects. It works great when my app is running but when I restart it...the collection is empty...Why does it not keep my object collection in memory.?
private void llsElements_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
LongListSelector llselement = null;
listElementCollection.Clear();
if (sender != null)
llselement =(LongListSelector)sender;
if(llselement.SelectedItem!=null)
{
BdeskElement bdelement=(BdeskElement)llselement.SelectedItem;
if (bdelement.TypeElement == BdeskElement.BdeskTypeElement.File)
{
if (!IsolatedStorageSettings.ApplicationSettings.Contains(bdelement.URLElement))//IsCached? =>NON
{
if (IsolatedStorageSettings.ApplicationSettings.Count >= 100)//Si la liste des fichiers en cache est pleine
{
string KeyOldestElement = IsolatedStorageSettings.ApplicationSettings.Last().Key;
IsolatedStorageSettings.ApplicationSettings.Remove(KeyOldestElement);//on supprime le dernier élément de la liste
if (IsolatedStorageOperations.IsFileSaved(KeyOldestElement+bdelement.Extension))
IsolatedStorageOperations.DeleteFile(KeyOldestElement+bdelement.Extension);
}
IsolatedStorageSettings.ApplicationSettings.Add(bdelement.URLElement, bdelement);//on ajoute notre élément
DownloadAndReadFile(bdelement);
}
else //Si le fichier est deja présent dans la liste (donc déja en cache)
{
if (IsFileModified(bdelement, IsolatedStorageSettings.ApplicationSettings[bdelement.URLElement]))//compare la version téléchargée et la version en cache/ les versions sont identiques
{
string FileNameFormated = bdelement.URLElement.Replace("/", "_").Substring(7, bdelement.URLElement.Length - 7);
if (IsolatedStorageOperations.IsFileSaved(FileNameFormated))
{
MessageBox.Show("Le fichier est lu dans le cache");
byte[] Encryptedbytefile = IsolatedStorageOperations.GetFile((FileNameFormated));
byte [] UnCryptedByteFile=EncryptedString.DecryptDataToData(Encryptedbytefile);
IsolatedStorageOperations.SaveToFile(UnCryptedByteFile, "FileToRead" + bdelement.Extension);
IsolatedStorageOperations.ReadFile("FileToRead"+bdelement.Extension);
}
}
else//les versions sont différentes
{
IsolatedStorageSettings.ApplicationSettings.Remove(bdelement.URLElement);//supprime l'ancienne version
IsolatedStorageSettings.ApplicationSettings.Add(bdelement.URLElement, bdelement);//ajoute la nouvelle
DownloadAndReadFile(bdelement);
}
}
}
else if (bdelement.TypeElement == BdeskElement.BdeskTypeElement.Folder)
{
//l'élément cliqué devient l'élément courant
App.CurrentFolder = bdelement;
//On raffiche la page
NavigationService.Navigate(new Uri(String.Format("/Views/BDocs/FolderView.xaml?id={0}", Guid.NewGuid().ToString()), UriKind.Relative));
}
}
IsolatedStorageSettings.ApplicationSettings.Save(); //EDITED
}
EDIT
ISSUE I HAVE ON THE SAVE METHOD
Informations supplémentaires : Type 'System.Windows.Media.ImageSource' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute. Alternatively, you can ensure that the type is public and has a parameterless constructor - all public members of the type will then be serialized, and no attributes will be required.
Probably it's because you aren't saving it - try to use IsolatedStorageSettings.ApplicationSettings.Save() when you finsh with it.
Of course it won't work when you restart the Emulator - after restarting it is a fresh instance.
EDIT - after OP's edit
You cannot serialize ImageSource - similar question is here. Instead consider serializing ImagePath.

Is asynchronous controller a viable option?

I currently have this huge and slow code that takes forever to run.... Im in the process of refactoring it to make it somewhat more readable and overall provide a faster experience...
Basically I have a folder in my server where text files are stored with a LOT of data.. this script will read from this files and through EF add elements to the database... this takes a long time to load.
public class ScriptMetabolicoController : Controller
{
private IPortalAdministradorServices _servicioPortalAdministrador = new PortalAdministradorServices();
IRepositorio<Historia> historiarepo = new Repositorio<Historia>();
IRepositorio<Indicador_Metabolico> indicadorrepo = new Repositorio<Indicador_Metabolico>();
[Authorize(Roles = "Administrador")]
public ActionResult Index()
{
DirectoryInfo myDir = new DirectoryInfo(Server.MapPath("labtelFTP"));
ViewData["sinActualizacionesPendientes"] = false;
if (myDir.GetFiles().Count() != 0)
{
try
{
foreach (FileInfo file in myDir.GetFiles())
{
if (file.Extension != ".aspx")
{
StreamReader stream;
stream = file.OpenText();
while (stream.Peek() != -1)
{
string linea = stream.ReadLine();
string cedula = linea.Substring(2, 18).Trim();
Historia historia;
if (historiarepo.ObtenerTodos().Where(h => h.Cedula == cedula).Count() == 1)
{
//Se obtiene la historia por la cédula del participante
historia = historiarepo.ObtenerTodos().Where(h => h.Cedula == cedula).First();
//Se inicializan las fechas de solicitud y las fechas de reporte del examen que se lee del archivo
var numero = historia.Examenes_Metabolicos.Count();
int anor = Convert.ToInt32(linea.Substring(216, 4));
int mesr = Convert.ToInt32(linea.Substring(220, 2));
int diar = Convert.ToInt32(linea.Substring(222, 2));
DateTime fecha_reporte = new DateTime(anor, mesr, diar);
int anos = Convert.ToInt32(linea.Substring(202, 4));
int mess = Convert.ToInt32(linea.Substring(206, 2));
int dias = Convert.ToInt32(linea.Substring(208, 2));
DateTime fecha_solicitud = new DateTime(anos, mess, dias);
//Variable que tendrá el examen en cuestión
Examen_Metabolico examen;
//Si es el primer indicador de un examen nuevo en la historia del participante se crea una instancia nueva
if (historia.Examenes_Metabolicos.Where(e => e.Fecha_Solicitud == fecha_solicitud).Count() == 0)
{
examen = new Examen_Metabolico();
examen.Fecha_Reporte = fecha_reporte;
examen.Fecha_Solicitud = fecha_solicitud;
historia.Examenes_Metabolicos.Add(examen);
//Se crea en base de datos el examen vacío para luego agregarle valores metabólicos asociados a el.
//historiarepo.GuardarTodo();
}
//Si el indicador no es el primero de un examen nuevo entonces se le asigna a la variable 'examen' la referencia del mismo
else
{
examen = historia.Examenes_Metabolicos.Where(e => e.Fecha_Solicitud == fecha_solicitud).First();
}
//Se lee el código del indicador metabólico
string codigo = linea.Substring(236, 6).Trim();
//Si en efecto el indicador presente en la línea que se está leyendo existe se prosigue a anexarlos al examen
if (indicadorrepo.ObtenerTodos().Where(i => i.Codigo == codigo).Count() != 0)
{
//Se carga el indicador con el que se está trabajando en una línea específica
Indicador_Metabolico indicador = indicadorrepo.ObtenerTodos().Where(i => i.Codigo == codigo).First();
//Se crea una nueva instancia de valor metabólico
Valor_Metabolico val = new Valor_Metabolico();
//Se obtienen los valores del indicador de la línea del archivo que se está leyendo
string rango_alto = linea.Substring(194, 6).Trim();
string rango_bajo = linea.Substring(188, 6).Trim();
string unidades = linea.Substring(178, 10).Trim();
bool alerta = false;
string al = linea.Substring(200, 2).Trim();
if (al != "")
alerta = true;
string valor = linea.Substring(118, 60).Trim();
//Se inicializan los atributos del valor metabólico
//val.Examen_Metabolico_Id = examen.Id;
//val.Indicador_Metabolico_Id = indicador.Id;
val.Unidades = unidades;
val.Rango_Alto = rango_alto;
val.Rango_Bajo = rango_bajo;
val.Valor = valor;
val.Alerta = alerta;
val.Indicador_Metabolico = indicador;
examen.Valores_Metabolicos.Add(val);
historiarepo.GuardarTodo();
}
}
}
stream.Close();
file.MoveTo(Path.Combine(Server.MapPath("BackuplabtelFTP"), file.Name));
}
}
}
catch (Exception e)
{
ViewData["Error"] = true;
return View();
}
ViewData["Error"] = false;
return View();
}
else
{
ViewData["sinActualizacionesPendientes"] = true;
return View();
}
}
}
I know there is a feature called asynchronous controllers but Im not sure if they are meant for this case...
Please give me some advice on how to make this better.
ps. I would also like this script to be called regularly (once an hour) but Im not sure how to that either..
Does this need to be done in MVC? It seems like this could maybe be done by something like a windows service (which could be kind of a pain) or some other kind of scheduled app, maybe even a console application. For repeating tasks that just do things with either file systems or the DB, I use a console application that uses System.Threading.Timers. You let the background timer threads work and your web app is free to do, well, web things :)
If it needs to be web based, here's a handy post for how to do simple repeating tasks.
https://blog.stackoverflow.com/2008/07/easy-background-tasks-in-aspnet/
If you wanted to turn your action into an asynchronous action then your controller would look something like this:
public class ScriptMetabolicoController : AsyncController
{
private IPortalAdministradorServices _servicioPortalAdministrador = new PortalAdministradorServices();
IRepositorio<Historia> historiarepo = new Repositorio<Historia>();
IRepositorio<Indicador_Metabolico> indicadorrepo = new Repositorio<Indicador_Metabolico>();
[Authorize(Roles = "Administrador")]
public void IndexAsync()
{
AsyncManager.OutstandingOperations.Increment();
DirectoryInfo myDir = new DirectoryInfo(Server.MapPath("labtelFTP"));
AsyncManager.Parameters["sinActualizacionesPendientes"] = false;
if (myDir.GetFiles().Count() != 0)
{
try
{
foreach (FileInfo file in myDir.GetFiles())
{
if (file.Extension != ".aspx")
{
StreamReader stream;
stream = file.OpenText();
while (stream.Peek() != -1)
{
string linea = stream.ReadLine();
string cedula = linea.Substring(2, 18).Trim();
Historia historia;
if (historiarepo.ObtenerTodos().Where(h => h.Cedula == cedula).Count() == 1)
{
//Se obtiene la historia por la cédula del participante
historia = historiarepo.ObtenerTodos().Where(h => h.Cedula == cedula).First();
//Se inicializan las fechas de solicitud y las fechas de reporte del examen que se lee del archivo
var numero = historia.Examenes_Metabolicos.Count();
int anor = Convert.ToInt32(linea.Substring(216, 4));
int mesr = Convert.ToInt32(linea.Substring(220, 2));
int diar = Convert.ToInt32(linea.Substring(222, 2));
DateTime fecha_reporte = new DateTime(anor, mesr, diar);
int anos = Convert.ToInt32(linea.Substring(202, 4));
int mess = Convert.ToInt32(linea.Substring(206, 2));
int dias = Convert.ToInt32(linea.Substring(208, 2));
DateTime fecha_solicitud = new DateTime(anos, mess, dias);
//Variable que tendrá el examen en cuestión
Examen_Metabolico examen;
//Si es el primer indicador de un examen nuevo en la historia del participante se crea una instancia nueva
if (historia.Examenes_Metabolicos.Where(e => e.Fecha_Solicitud == fecha_solicitud).Count() == 0)
{
examen = new Examen_Metabolico();
examen.Fecha_Reporte = fecha_reporte;
examen.Fecha_Solicitud = fecha_solicitud;
historia.Examenes_Metabolicos.Add(examen);
//Se crea en base de datos el examen vacío para luego agregarle valores metabólicos asociados a el.
//historiarepo.GuardarTodo();
}
//Si el indicador no es el primero de un examen nuevo entonces se le asigna a la variable 'examen' la referencia del mismo
else
{
examen = historia.Examenes_Metabolicos.Where(e => e.Fecha_Solicitud == fecha_solicitud).First();
}
//Se lee el código del indicador metabólico
string codigo = linea.Substring(236, 6).Trim();
//Si en efecto el indicador presente en la línea que se está leyendo existe se prosigue a anexarlos al examen
if (indicadorrepo.ObtenerTodos().Where(i => i.Codigo == codigo).Count() != 0)
{
//Se carga el indicador con el que se está trabajando en una línea específica
Indicador_Metabolico indicador = indicadorrepo.ObtenerTodos().Where(i => i.Codigo == codigo).First();
//Se crea una nueva instancia de valor metabólico
Valor_Metabolico val = new Valor_Metabolico();
//Se obtienen los valores del indicador de la línea del archivo que se está leyendo
string rango_alto = linea.Substring(194, 6).Trim();
string rango_bajo = linea.Substring(188, 6).Trim();
string unidades = linea.Substring(178, 10).Trim();
bool alerta = false;
string al = linea.Substring(200, 2).Trim();
if (al != "")
alerta = true;
string valor = linea.Substring(118, 60).Trim();
//Se inicializan los atributos del valor metabólico
//val.Examen_Metabolico_Id = examen.Id;
//val.Indicador_Metabolico_Id = indicador.Id;
val.Unidades = unidades;
val.Rango_Alto = rango_alto;
val.Rango_Bajo = rango_bajo;
val.Valor = valor;
val.Alerta = alerta;
val.Indicador_Metabolico = indicador;
examen.Valores_Metabolicos.Add(val);
historiarepo.GuardarTodo();
}
}
}
stream.Close();
file.MoveTo(Path.Combine(Server.MapPath("BackuplabtelFTP"), file.Name));
}
}
AsyncManager.Parameters["Error"] = false;
}
catch (Exception e)
{
AsyncManager.Parameters["Error"] = true;
}
}
else
{
AsyncManager.Parameters["sinActualizacionesPendientes"] = true;
}
AsyncManager.OutstandingOperations.Decrement();
}
public ActionResult IndexCompleted(bool error, bool sinActualizacionesPendientes )
{
ViewData["sinActualizacionesPendientes"] = sinActualizacionesPendientes;
ViewData["Error"] = error;
return View();
}
}
And here's the MS article that could help you out:
http://msdn.microsoft.com/en-us/library/ee728598.aspx
Hope this helps!
Check this out it may help http://quartznet.sourceforge.net/ . I plan on using it in my current project to schedule up some longer running tasks.

Browser Progress Tracker in Blackberry BrowserField

Has anyone been able to implement a progress tracker bar for a BrowserField in blackberry i'm struggling with this. I want to tell the user that something is actually happening when he opens the browser field in my app.
I already checked the BrowserFieldProgressBar demo, but that only works on OS 6 since it's for BrowserField2. Either a progress bar or a dialog with a gif works. I tried implementing the following, but the PopUpScreen doesn't show over the browser field and once i exit the browserfield it gets stuck:
public class BrowserPopUpScreen extends MainScreen{
private GridFieldManager _manager;
private BrowserField _browserField; // Campo de la interfaz que se utiliza para mostrar una página web
private final String GRAPH_URL = "https://graph.facebook.com"; // URL de Graph API de Facebook.
private final String NEXT_URL = "http://www.facebook.com/connect/login_success.html"; // URL adonde se redirige al usuario cuando la operacion es exitosa.
private final String APPLICATION_KEY = "e1812f3b71678c8e0017831cc4cbc87a"; // Llave de la aplicación en Facebook.
private final String APPLICATION_SECRET = ""; // Secreto de la aplicacion en Facebook.
private final String APPLICATION_ID = ""; // ID de la aplicacion en Facebook.
public static final int FACEBOOK_SIGNUP = 1; // Constante que indica que el usuario desea registrarse con Facebook
public static final int FACEBOOK_LINK = 2; // Constante que indica que el usuario desea conectar su cuenta de Facebook a la cuenta de Social Voice
/**
* Construye la pantalla de browser.
*
* Dependiendo te la acción que recibe en caso de ser FACEBOOK_SIGNUP
* peticiona en envió de información de usuario a Facebook, en caso contrario
* solo pide un token de acceso para el usuario.
*
* #param manager
* Administrador de contenido que utilizará la pantalla
* #param action
* Acción que se realizará, en caso de ser registro la
* acción será FACEBOOK_SIGNUP, en caso de solo conectar
* Fonyk con Facebook será FACEBOOK_LINK
*/
public BrowserPopUpScreen(GridFieldManager manager, final int action)
{
_manager = manager;
_browserField = new BrowserField();
// Se crea el URL de la petición y se hace el llamado a dicho URL
_browserField.requestContent(new StringBuffer().append("https://graph.facebook.com/oauth/authorize?client_id=").append(APPLICATION_ID).append("&").append("redirect_uri=").append(NEXT_URL).append("&").append("scope=offline_access,publish_stream,email").append("&display=wap").toString());
final LoadingScreen loadingScreen = new LoadingScreen();
//Este metodo detecta cuando se realiza un cambio en el BrowserField
BrowserFieldListener browserListener = new BrowserFieldListener() {
public void documentLoaded(BrowserField browserField, Document document) throws Exception
{
loadingScreen.onClose();
//Se verifica si es nuestro URL de redirección
if(_browserField.getDocumentUrl().startsWith(NEXT_URL))
{
String url = _browserField.getDocumentUrl();
String code = getElement(url, "code");
//Si la petición fue exitosa al URL se le agrega un campo code
//revisamos si este no es uno para continuar con la operacion
if(!code.equals(""))
{
//Creamos un cliente http para hacer un GET y obtener el token
//de acceso
HttpClient httpClient = new HttpClient(MainApp.connFactory);
//Se crea un hashtable que va a contener la información de nuestra aplicación
//y el código proporcionado previamente
Hashtable data = new Hashtable();
data.put("client_id", APPLICATION_ID);
data.put("redirect_uri", NEXT_URL);
data.put("client_secret", APPLICATION_SECRET);
data.put("code", code);
StringBuffer response = httpClient.doGet(GRAPH_URL.concat("/oauth/access_token"), data);
if(response.length() == 0)
throw new Exception();
//Se obtiene el token de acceso de la respuesta y se asigna a nuestro
//objeto usuario
String accessToken = getElement(response.toString(), "access_token");
MainApp.user.setFacebookAccessToken(accessToken);
MainApp.user.setFacebookAccess(true);
//Si la acción a realizar es de registro, se utiliza el token de acceso
//para peticionar los datos del usuario a FB
if(action == FACEBOOK_SIGNUP)
{
data.clear();
data.put("access_token", accessToken);
response = null;
response = httpClient.doGet(GRAPH_URL.concat("/me"), data);
JSONObject jsonResponse = new JSONObject(response.toString());
// Al obtener una respuesta se establecen los valores en el objeto definido
// inicialmente en la aplicacion
MainApp.facebookUserInfo.setFirstName(jsonResponse.optString("first_name"));
MainApp.facebookUserInfo.setLastBame(jsonResponse.optString("last_name"));
MainApp.facebookUserInfo.setEmail(jsonResponse.optString("email"));
// MainApp.facebookUserInfo.set_birthday(jsonResponse.optString("birthday"));
MainApp.facebookUserInfo.setGender(jsonResponse.optString("gender"));
MainApp.facebookUserInfo.setMiddleName(jsonResponse.optString("middle_name"));
MainApp.facebookUserInfo.setLink(jsonResponse.optString("link"));
}
//Se invoca a la aplicación para cerrar esta pantalla después de
//completarse la operación
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
UiApplication.getUiApplication().getActiveScreen().close();
}
});
}
}
}
};
_browserField.addListener(browserListener);
add(_browserField);
UiApplication.getUiApplication().pushScreen(loadingScreen);
}
private class LoadingScreen extends PopupScreen{
private AnimatedGIFField _loader;
public LoadingScreen(){
super(new VerticalFieldManager());
GIFEncodedImage ourAnimation = (GIFEncodedImage) GIFEncodedImage.getEncodedImageResource("ajax-loader (7).gif");
_loader = new AnimatedGIFField(ourAnimation, Field.FIELD_HCENTER);
this.add(_loader);
}
public boolean onClose() {
setDirty(false);
return super.onClose();
}
}
/**
* Extra un valor especificado del URL
*
* #param url
URL del cuál se va a extraer un valor
* #param element
Elemento que se desea obtener
* #return
*/
private String getElement(String url, String element)
{
int startIndex = url.indexOf(element);
if (startIndex > -1) {
int stopIndex = url.length();
if (url.indexOf('&', startIndex) > -1) {
stopIndex = url.indexOf('&', startIndex);
} else if (url.indexOf(';', startIndex) > -1) {
stopIndex = url.indexOf(';', startIndex);
}
element = url.substring(url.indexOf('=', startIndex) + 1, stopIndex);
return element;
}
return "";
}
}
At a glance 4 things look odd to me:
1). you are settings listener AFTER you have requested the content (so it may happen the page can be loaded BEFORE the listener will be able to react).
2). loadingScreen.onClose(); - what do you expect this will do?
3). UiApplication.getUiApplication().getActiveScreen().close(); - are you really sure which screen you are closing? Looks like your are an incurable optimist.
4). I've never used BrowserFieldListener, so this point is just a guess: BrowserFieldListener has other callbacks including those for failures. So what if BrowserField.requestContent(String url) fails (there could be a dozen potential reasons for that) - will that end up calling the same callback you are using?
UPDATE:
I guess the problem is all what you're doing happens sequentially on a UI-thread. So you push a progress screen, do sthm useful, then close the progress - all this happens on a UI-thread sequentially. In this case you do not give the UI-framework a chance to actually display/draw the progress screen. To draw a screen the UI-thread needs some FREE cpu time. When UI-thread comes to the point where it could start drawing the progress screen it finds there is no need to do it already, because the progress screen has been closed.
A simple (and dirty) workaround would be to call UiApplication.repaint() right after you push the progress screen. This method does the following:
Repaints entire display.
Invoke this method to repaint the
entire display. It invalidates, and
then paints, each screen on the
display stack.

Resources