I'm writing a celsius-farenheits converter but the program crashes for something that I didn't found
I'm actually trying to use the data binding and the view model but Android Studio founded some issues in ActivityMainBindingImpl.java that I didn't write by myself. Here's the part of code were it founds a problem. It is in line 104 at com.example.convertitorecelsius_farenheit.MainViewModel viewModel = mViewModel; It says "Cannot resolve symbol 'mViewModel'"
There's another problem in line 33 in "super(bindingComponent, root, 0", it says "'ActivityMainBinding()' has private access in 'com.example.convertitorecelsius_farenheit.databinding.ActivityMainBinding'"
The last problem is at line 8 in "public class ActivityMainBindingImpl extends ActivityMainBinding {", the error is in "ActivityMainBinding", it says "Cannot inherit from final 'com.example.convertitorecelsius_farenheit.databinding.ActivityMainBinding'"
Here's the full code where I founded these problems
package com.example.convertitorecelsius_farenheit.databinding;
import com.example.convertitorecelsius_farenheit.R;
import com.example.convertitorecelsius_farenheit.BR;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
#SuppressWarnings("unchecked")
public class ActivityMainBindingImpl extends ActivityMainBinding {
#Nullable
private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
#Nullable
private static final android.util.SparseIntArray sViewsWithIds;
static {
sIncludes = null;
sViewsWithIds = new android.util.SparseIntArray();
sViewsWithIds.put(R.id.cambiaTemperatura, 3);
sViewsWithIds.put(R.id.inputTemperatura, 4);
sViewsWithIds.put(R.id.converti, 5);
}
// views
#NonNull
private final androidx.constraintlayout.widget.ConstraintLayout mboundView0;
// variables
// values
// listeners
// Inverse Binding Event Handlers
public ActivityMainBindingImpl(#Nullable androidx.databinding.DataBindingComponent bindingComponent, #NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 6, sIncludes, sViewsWithIds));
}
private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 0
, (android.widget.Button) bindings[3]
, (android.widget.Button) bindings[5]
, (android.widget.EditText) bindings[4]
, (android.widget.TextView) bindings[2]
, (android.widget.TextView) bindings[1]
);
this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];
this.mboundView0.setTag(null);
this.textTemperatura.setTag(null);
this.textView.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
#Override
public void invalidateAll() {
synchronized(this) {
mDirtyFlags = 0x2L;
}
requestRebind();
}
#Override
public boolean hasPendingBindings() {
synchronized(this) {
if (mDirtyFlags != 0) {
return true;
}
}
return false;
}
#Override
public boolean setVariable(int variableId, #Nullable Object variable) {
boolean variableSet = true;
if (BR.viewModel == variableId) {
setViewModel((com.example.convertitorecelsius_farenheit.MainViewModel) variable);
}
else {
variableSet = false;
}
return variableSet;
}
public void setViewModel(#Nullable com.example.convertitorecelsius_farenheit.MainViewModel ViewModel) {
this.mViewModel = ViewModel;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.viewModel);
super.requestRebind();
}
#Override
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
switch (localFieldId) {
}
return false;
}
#Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String viewModelTypeCurrentTemperature = null;
int viewModelConvertiTemperatura = 0;
com.example.convertitorecelsius_farenheit.MainViewModel viewModel = mViewModel;
if ((dirtyFlags & 0x3L) != 0) {
if (viewModel != null) {
// read viewModel.typeCurrentTemperature
viewModelTypeCurrentTemperature = viewModel.getTypeCurrentTemperature();
// read viewModel.convertiTemperatura()
viewModelConvertiTemperatura = viewModel.convertiTemperatura();
}
}
// batch finished
if ((dirtyFlags & 0x3L) != 0) {
// api target 1
this.textTemperatura.setText(viewModelConvertiTemperatura);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.textView, viewModelTypeCurrentTemperature);
}
}
// Listener Stub Implementations
// callback impls
// dirty flag
private long mDirtyFlags = 0xffffffffffffffffL;
/* flag mapping
flag 0 (0x1L): viewModel
flag 1 (0x2L): null
flag mapping end*/
//end
}
Here's the program I wrote
MainActivity.java
package com.example.convertitorecelsius_farenheit;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.widget.EditText;
import com.example.convertitorecelsius_farenheit.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private MainViewModel viewModel;
public EditText inputTemperature;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
inputTemperature = findViewById(R.id.inputTemperatura);
binding = ActivityMainBinding.inflate(getLayoutInflater());
binding.setLifecycleOwner(this);
setContentView(binding.getRoot());
viewModel = new ViewModelProvider(this).get(MainViewModel.class);
binding.setViewModel(viewModel);
}
public int getInputTemperature() {
return Integer.parseInt(inputTemperature.toString());
}}
MainViewModel.java
package com.example.convertitorecelsius_farenheit;
import androidx.lifecycle.ViewModel;
public class MainViewModel extends ViewModel {
public int grades;
public boolean isCelsius = false;
MainActivity temperaturaInserita = new MainActivity();
//private final MutableLiveData<String> _TypeCurrentTemperatura = new MutableLiveData<>();
private String _TypeCurrentTemperatura = ""; //indicates if the temperature is celsius or farenheit
public String getTypeCurrentTemperature() {
return _TypeCurrentTemperatura;
}
public void changeTypeTemperature() {
if (isCelsius) {
isCelsius = false;
_TypeCurrentTemperatura = "F°";
} else {
isCelsius = true;
_TypeCurrentTemperatura = "C°";
}
}
public int convertiTemperatura() { //convertTemperature (that's the italian name)
if (isCelsius) {
grades = (int) ((temperaturaInserita.getInputTemperature() * 1.8) + 32);
} else {
grades = (int) ((int) ((temperaturaInserita.getInputTemperature()) -32) * .5556);
}
return grades;
}}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.example.convertitorecelsius_farenheit.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="348dp"
android:text="#{viewModel.typeCurrentTemperature}"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/cambiaTemperatura"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="80dp"
android:text="c--f"
app:layout_constraintBottom_toTopOf="#+id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent" />
<EditText
android:id="#+id/inputTemperatura"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="52dp"
android:ems="10"
android:inputType="number"
app:layout_constraintBottom_toTopOf="#+id/cambiaTemperatura"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="#+id/converti"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="273dp"
android:layout_marginBottom="274dp"
android:text="converti"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/inputTemperatura"
app:layout_constraintVertical_bias="1.0" />
<TextView
android:id="#+id/textTemperatura"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="23dp"
android:text="#{viewModel.convertiTemperatura()}"
android:textSize="24sp"
app:layout_constraintBottom_toTopOf="#+id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Can somebody help me :)
There are some things wrong in your code:
You're setting the activity view twice, remove the first line: setContentView(R.layout.activity_main);
Since you're using view binding (different thing from data binding), you dont need to call findViewById replace it with inputTemperature = binding.inputTemperatura
If you're already using data binding why bother with view binding? You can do all input/output related tasks in data binding.
You SHOULD NEVER instantiate ANDROID activities or hold a reference to it, this is task of the Android framework, remove the line MainActivity temperaturaInserita = new MainActivity(); of your viewmodel.
Check this answer it may help you: Android : Difference between DataBinding and ViewBinding
EDIT
You don't need to call methods of your activity from your viewmodel, this is a bad practice, because if the system destroys your activity you will end with a NPE in your view model, you have 2 options:
Use two way data binding to set/get the value of the temperature from the viewmodel: https://bignerdranch.com/blog/two-way-data-binding-on-android-observing-your-view-with-xml/
Change the function in the view model to receive as argument the value of the input text.
I recommend to go with first option, this way you will have the updated value always in your view model and also can survive config changes.
And remember don't matter what, your viewmodel SHOULD never have a reference to the Activity.
Related
I am making an android app, it has an editText on the top and a recyclerview on the buttom.
Every time when a user hit "enter" in the editText field, I will then make an API call with OKHttp and save the info I needed to a Singleton Class so my adapter can use it and update the list.
However, the recyclerview is not showing(updating) if the focus is not in the editText
"V/ViewRootImpl: The specified message queue synchronization barrier token has not been posted or has already been removed"
It's showing this everytime when I click on the editText, I googled it and found out it might be the thread issues. The only place I interact with thread is the API request I made with okHTTP. However, the request returns and interpret the json file correctly.
public Boolean makeCall (String parameter) throws IOException, UnirestException {
// make the api call
String completedLink = BASE_LINK + parameter;
Future<HttpResponse<com.mashape.unirest.http.JsonNode>> response = Unirest.get("https://mashape-community-urban-dictionary.p.rapidapi.com/define?term=wat")
.header("x-rapidapi-host", "mashape-community-urban-dictionary.p.rapidapi.com")
.header("x-rapidapi-key", "xxxxx")
.asJsonAsync(new Callback<com.mashape.unirest.http.JsonNode>() {
#Override
public void completed(HttpResponse<com.mashape.unirest.http.JsonNode> response) {
// decode the response body
ObjectMapper objectMapper = new ObjectMapper();
JsonNode node = null;
try {
node = objectMapper.readTree(String.valueOf(response.getBody()));
} catch (JsonProcessingException ex) {
ex.printStackTrace();
}
JsonNode resultNode = node.get("list");
List<WordItem> resultList = null;
try {
resultList = objectMapper.readValue(resultNode.toString(),
new TypeReference<List<WordItem>>() {});
} catch (JsonProcessingException ex) {
ex.printStackTrace();
}
SavedInfo.getInstance().setResult(resultList);
}
#Override
public void failed(UnirestException e) {
}
#Override
public void cancelled() {
}
});
return true;
}
Every time when I hit enter on the soft keyboard, my code will start making the API call
mResult.setAdapter(adapter);
mResult.setNestedScrollingEnabled(false);
mResult.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL));
mResult.setLayoutManager(new LinearLayoutManager(getContext()));
mSearchBox.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {
if (i == EditorInfo.IME_ACTION_DONE) {
// show progress bar
hideProgressBar(false, mProgress);
mHomeText.setText(textView.getText());
try {
if (new HttpRequest().makeCall(textView.getText().toString())){
adapter.updateList();
}
else {
// showing error message
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
hideProgressBar(true, mProgress);
// hide keyword when enter key is detected
View view = getActivity().getCurrentFocus();
if (view != null) {
InputMethodManager imm = (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
return true;
}
});
Also, one thing to point out is the Progress Bar(I set it to "VISIBLE" but not showing still)is not showing at all. I set a few places throughout my code, the Singleton Class and Http Requests are correct and return + save correct info.
Here is my updateList() in my adapter class just in case this helps
public void updateList () {
list.clear();
list.addAll(SavedInfo.getInstance().getResult());
notifyDataSetChanged();
}
Here is the my layout file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.home.HomeFragment"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#F3F3F3"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="#+id/title_word_home"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="36dp"
android:fontFamily="sans-serif"
android:text="#string/home_text"
android:textAlignment="center"
android:textSize="28sp"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="0dp" />
<EditText
android:id="#+id/searchBox"
android:layout_width="254dp"
android:layout_height="50dp"
android:layout_marginStart="239dp"
android:layout_marginEnd="239dp"
android:autofillHints=""
android:drawableStart="#drawable/ic_search"
android:drawablePadding="10dp"
android:ems="10"
android:hint="#string/searchBox_hint"
android:imeOptions="actionDone"
android:inputType="text"
android:pointerIcon="none"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/title_word_home" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ProgressBar
android:id="#+id/loading_progress"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center|center_vertical"
android:indeterminate="true"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/home_resultList"
android:layout_width="409dp"
android:layout_height="wrap_content"
android:visibility="visible" />
</LinearLayout>
</LinearLayout>
Thank you for reading!
The problem occurs because the data posts to recycler view before that has downloaded. You are trying to update the recycler view in the method updateList() which called after the request started but before it completed. That's also why the progress bar doesn't show - it's immediately gone.
In order to fix the problem, you have to trigger updateList() after the data has been downloaded. The good way will be to rewrite the makeCall() method, which accepts the second argument as a callback, so you can update the list.
Define the custom callback:
interface MyCallback {
void completed(List<WordItem> result);
void error(UnirestException error);
}
Rewrite making call a bit:
public void makeCall(String parameter, MyCallback callback) {
// make the api call
String completedLink = BASE_LINK + parameter;
Future<HttpResponse<com.mashape.unirest.http.JsonNode>> response = Unirest.get("https://mashape-community-urban-dictionary.p.rapidapi.com/define?term=wat")
.header("x-rapidapi-host", "mashape-community-urban-dictionary.p.rapidapi.com")
.header("x-rapidapi-key", "xxxxx")
.asJsonAsync(new Callback<com.mashape.unirest.http.JsonNode>() {
#Override
public void completed(HttpResponse<com.mashape.unirest.http.JsonNode> response) {
// decode the response body
ObjectMapper objectMapper = new ObjectMapper();
JsonNode node = null;
try {
node = objectMapper.readTree(String.valueOf(response.getBody()));
} catch (JsonProcessingException ex) {
ex.printStackTrace();
}
JsonNode resultNode = node.get("list");
List<WordItem> resultList = null;
try {
resultList = objectMapper.readValue(resultNode.toString(),
new TypeReference<List<WordItem>>() {});
} catch (JsonProcessingException ex) {
ex.printStackTrace();
}
// SavedInfo.getInstance().setResult(resultList);
callback.completed(resultList);
}
#Override
public void failed(UnirestException e) {
callback.failed(e);
}
#Override
public void cancelled() {
}
});
}
And finally pass the callback to makeCall() and refresh the list:
#Override
public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {
if (i == EditorInfo.IME_ACTION_DONE) {
// show progress bar
hideProgressBar(false, mProgress);
// clear your list before call if you wish
// updateList(null);
mHomeText.setText(textView.getText());
new HttpRequest().makeCall(textView.getText().toString(), new MyCallback() {
#Override
public void completed(List<WordItem> result) {
updateList(result);
hideProgressBar(true, mProgress);
}
#Override
public void error(UnirestException error) {
hideProgressBar(true, mProgress);
// showing error message
}
});
// hide keyword when enter key is detected
View view = getActivity().getCurrentFocus();
if (view != null) {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
return true;
}
This method should be rewrited as well:
public void updateList(List<WordItem> result) {
list.clear();
if (result != null) list.addAll(result);
notifyDataSetChanged();
}
After that problem should go away and also you have no longer need in the singleton class that holding your data.
By the way, there is the Retrofit library which used in order to make api requests in an easier way. The retrofit is the best practice in the android community, so I suggest to try use it.
I'm relatively new to Android development and I've been stuck on this for a couple of weeks.
I'm working with a JSON Array and I've been able to log that I am returning the JSON Array with an API call and that it is breaking up the array into separate JSON Objects.
Hero JSON {"PrimaryName":"Abathur","ImageURL":"Abathur","AttributeName":"Abat","Group":"Specialist","SubGroup":"Utility","Translations":"Abatur,АБАТУР,아바투르,阿巴瑟"}
Hero JSON {"PrimaryName":"Alarak","ImageURL":"Alarak","AttributeName":"Alar","Group":"Assassin","SubGroup":"Ambusher","Translations":"亞拉瑞克,阿拉纳克,알라라크,Аларак"}
etc...
From what it appears it is converting those JSON Objects into Java Objects.
hotsbuddy.HeroDataModel#f27ef8a
hotsbuddy.HeroDataModel#db939fb
etc...
When I changed the Log to return the Hero Name, Hero Image and Hero Group from my
public static HeroDataModel fromJson(JSONObject jsonObject)
method I get this:
03-24 18:44:53.828 30539-30539/com.timfreebernii.hotsbuddy D/HoTS: Hero Abathur Abathur Specialist
03-24 18:44:53.828 30539-30539/com.timfreebernii.hotsbuddy D/HoTS: Hero Alarak Alarak Assassin
03-24 18:44:53.828 30539-30539/com.timfreebernii.hotsbuddy D/HoTS: Hero Alexstrasza Alexstrasza Support
So I can see that I am getting Java Objects back.
These logs are being placed in my HeroDataModel class. However, I'm having an issue attaching these Java Objects to my ListView and ListView Adapter.
When I log my ArrayList creation in my API call I am getting Java Objects returned.
Array [com.timfreebernii.hotsbuddy.HeroDataModel#f27ef8a,
com.timfreebernii.hotsbuddy.HeroDataModel#db939fb, etc...]
These objects are not showing up in App View. I'm not getting a list just a blank white background with the blue bar with my app name from the Main Layout.
I've been using these guides from CodePath but I'm just not quite able to finish off this feature.
https://guides.codepath.com/android/Using-an-ArrayAdapter-with-ListView#row-view-recycling
https://guides.codepath.com/android/Converting-JSON-to-Models#bonus-setting-up-your-adapter
I know I'm using a different type of Adapter but I was kind of following along from a couple simple apps I built from a Udemy course I completed.
Here is the API I'm working with:
https://api.hotslogs.com/Public/Data/Heroes
My GitHub repo:
https://github.com/tfreebern2/hotsbuddy
Here is my HeroDataModel code:
public class HeroDataModel {
private String mHeroName;
private String mHeroImage;
private String mHeroGroup;
public String getHeroName() {
return this.mHeroName;
}
public String getHeroImage() {
return this.mHeroImage;
}
public String getHeroGroup() {
return this.mHeroGroup;
}
public static HeroDataModel fromJson(JSONObject jsonObject) {
HeroDataModel h = new HeroDataModel();
try {
h.mHeroName = jsonObject.getString("PrimaryName");
h.mHeroImage = jsonObject.getString("ImageURL");
h.mHeroGroup = jsonObject.getString("Group");
} catch (JSONException e) {
e.printStackTrace();
return null;
}
return h;
}
public static ArrayList<HeroDataModel> fromJson(JSONArray jsonObjects) {
JSONObject heroJson;
ArrayList<HeroDataModel> heroes = new ArrayList<HeroDataModel> . ();
for (int i = 0; i < jsonObjects.length(); i++) {
try {
heroJson = jsonObjects.getJSONObject(i);
// Log.d("HotS", "Hero JSON " + heroJson);
} catch (JSONException e) {
e.printStackTrace();
continue;
}
HeroDataModel hero = HeroDataModel.fromJson(heroJson);
// Log.d("HoTS", "Hero " + hero);
if (hero != null) {
heroes.add(hero);
}
}
// Log.d("HoTS", "Heroes Array" + heroes);
return heroes;
}
}
My HeroListAdapter:
public class HeroListAdapter extends ArrayAdapter<HeroDataModel> {
public HeroListAdapter(HeroListActivity context, ArrayList<HeroDataModel> heroes) {
super(context, 0, heroes);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
HeroDataModel currentHero = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(
R.layout.hero_list_item, parent, false);
}
TextView heroNameView = convertView.findViewById(R.id.hero_name);
TextView heroImageView = convertView.findViewById(R.id.hero_image);
TextView heroGroupView = convertView.findViewById(R.id.hero_group);
heroNameView.setText(currentHero.getHeroName());
heroImageView.setText(currentHero.getHeroImage());
heroGroupView.setText(currentHero.getHeroGroup());
return convertView;
}
}
My HeroListActivity:
public class HeroListActivity extends AppCompatActivity {
final String HEROES_URL = "https://api.hotslogs.com/Public/Data/Heroes";
ArrayList<HeroDataModel> heroes;
HeroListAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.hero_list);
heroListAPI(HEROES_URL);
ListView listView = (ListView) findViewById(R.id.lvHeroes);
heroes = new ArrayList<HeroDataModel>();
adapter = new HeroListAdapter(this, heroes);
listView.setAdapter(adapter);
}
private void heroListAPI(String url) {
AsyncHttpClient client = new AsyncHttpClient();
client.get(url, new JsonHttpResponseHandler() {
#Override
public void onSuccess(int statusCode, Header[] headers, JSONArray response) {
ArrayList<HeroDataModel> heroes = HeroDataModel.fromJson(response);
heroes.clear(); // clear existing items if needed
heroes.addAll(HeroDataModel.fromJson(response)); // add new items
adapter.notifyDataSetChanged();
}
#Override
public void onFailure(int statusCode, Header[] headers, Throwable e, JSONArray response) {
Log.e("HoTS", "Fail " + e.toString());
Log.d("HoTS", "Status code " + statusCode);
Toast.makeText(HeroListActivity.this, "Request Failed", Toast.LENGTH_SHORT).show();
}
});
}
}
My Hero_List Layout File:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/rlLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${packageName}.${activityClass}" >
<ListView
android:id="#+id/lvHeroes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true" >
</ListView>
</RelativeLayout>
My Hero_List_Item Layout File:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.timfreebernii.hotsbuddy.HeroActivity">
<TextView
android:id="#+id/hero_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hero Name"
/>
<TextView
android:id="#+id/hero_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hero Image"
/>
<TextView
android:id="#+id/hero_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hero Group"
/>
</LinearLayout>
Use Gson Library or retrofit to convert json objects into model class
Well I found a solution with some help.
My HeroListActivity is where I was making the API call and trying to convert the Json into Java Objects.
Here I declared my adapter in the HeroListActivity class outside of my onCreate method and removed the declaration of the ArrayList. I created a new HeroListAdapter using 'this' and 'new' ArrayList() as parameters. Then I attached the adapter to my ListView.
public class HeroListActivity extends AppCompatActivity {
final String HEROES_URL = "https://api.hotslogs.com/Public/Data/Heroes";
HeroListAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.hero_list);
heroListAPI(HEROES_URL);
ListView listView = (ListView) findViewById(R.id.lvHeroes);
adapter = new HeroListAdapter(this, new ArrayList<HeroDataModel>());
listView.setAdapter(adapter);
}
... more code
}
In my onSuccess method I created an ArrayList named 'myHeroes' with my renamed fromJsonToModelList.
#Override
public void onSuccess(int statusCode, Header[] headers, JSONArray response) {
ArrayList<HeroDataModel> myHeroes = HeroDataModel.fromJsonToModelList(response);
adapter.clear();
adapter.addAll(myHeroes);
adapter.notifyDataSetChanged();
}
I clear the adapter of any previous data, add all objects to the adapter and notify the adapter of any changes.
Some of the changes were just refactoring suggestions.
Ultimately, I wasn't passing the data to my adapter correctly nor do I think creating it correctly in my onCreate method.
I'm developing an Android app using Xamarin studio, but this is not important.
I use the pageViewer to upload the fragment (12 more or less). To the one display fragment i use a webView to display a local html page and with a swipe to the left, the webview content change to the next one.
So, at the fifth frgament memory problems started even if i used different tasks in my code.
My question is: Is there a way to 'detach' the fragment when i'm not displaying them? Can they not remain into my memory?
Thanks for all
This is my code, N.B: Java answers are accepeted as well
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
SetContentView (Resource.Layout.BookView);
_loader = ProgressDialog.Show (this, "Loading...", "Please wait...", true);
//return chapters count
chapters = 12 //example
//var dp = (int)Resources.DisplayMetrics.Density;
_layout = FindViewById<DrawerLayout> (Resource.Id.drawer_layout);
_view = FindViewById<ViewPager>(Resource.Id.bView);
_view.SetBackgroundColor (Color.White);
_currentAdapter = new AwesomeFragmentAdapter (SupportFragmentManager, path, name, chapters, this, _view);
_view.Adapter = _currentAdapter;
_view.OffscreenPageLimit = chapters;
List<int> positions = new List<int> ();
_view.PageSelected += (object sender, ViewPager.PageSelectedEventArgs e) => {
//get details
var page_load = new Task (() => {
//return an object with the chapter details
_chap = object;
});
page_load.Start();
//find the webview
_web = (WebView)_view.FindViewWithTag(300 + e.Position);
WebSettings setting = _web.Settings;
setting.CacheMode = CacheModes.Default;
setting.JavaScriptEnabled = true;
setting.BuiltInZoomControls = true;
setting.DisplayZoomControls = false;
setting.PluginsEnabled = true;
setting.SetPluginState(WebSettings.PluginState.On);
//setting.JavaScriptCanOpenWindowsAutomatically = true;
if (positions.Contains(e.Position)) {
_web.ClearCache(true);
_web.ClearView();
}
//Start when the scroll is finished
_view.PageScrollStateChanged += (object sendero, ViewPager.PageScrollStateChangedEventArgs ex) => {
if (ex.State == 0 ) {
if (positions.Contains(e.Position)) {
//_web.Reload(); --> doesn't work
//Doesn't reload the .js animations
_web.LoadUrl ("file://" + path + "/" + _chap.Name);
} else {
_web.LoadUrl ("file://" + path + "/" + _chap.Name);
positions.Add(e.Position);
}
}
};
};
}
public class BWebClient : WebViewClient
{
int _position;
string _path;
Activity _parent;
ViewPager _pager;
string _chapName;
public BWebClient (int position, string Path, Activity Parent, ViewPager Pager, string ChapName){
_position = position;
_parent = Parent;
_path = Path;
_pager = Pager;
_chapName = ChapName;
}
public override void OnPageFinished (WebView view, string url)
{
base.OnPageFinished (view, url);
view.ScrollTo (0, _position);
}
public override bool ShouldOverrideUrlLoading (WebView view, string url)
{
if (url.StartsWith ("navigate")) {
string destination = url.Substring (url.IndexOf ("navigate://") + "navigate://".Length);
int DestinationChapter = Int32.Parse (destination.Substring (0, destination.IndexOf("_")));
int l = destination.IndexOf("_") + 1;
int b = destination.Length - l;
int DestinationPage = Int32.Parse (destination.Substring (l,b));
if (DestinationPage == 0) {
_pager.SetCurrentItem(DestinationChapter ,true);
WebView _web = (WebView)_pager.FindViewWithTag(300 + DestinationChapter);
_web.LoadUrl ("file://" + _path + "/" + _chapName);
}
} else if (url.StartsWith ("pdf")) {
string file_path = System.IO.Path.Combine (_path, url.Substring (url.IndexOf ("pdf://") + "pdf://".Length));
Android.Net.Uri pdfFile = Android.Net.Uri.FromFile (new Java.IO.File (file_path));
Intent pdfIntent = new Intent (Intent.ActionView);
pdfIntent.SetDataAndType (pdfFile, "application/pdf");
_parent.StartActivity (pdfIntent);
}
return true;
}
}
public class AwesomeFragmentAdapter : FragmentPagerAdapter
{
string _path;
string _filename;
int _chapters;
Activity _parent;
FileUtilities _fUtils;
ViewPager _pager;
public AwesomeFragmentAdapter (Android.Support.V4.App.FragmentManager fm,
string path,
string filename,
int chapters,
Activity parent,
FileUtilities FUtils,
ViewPager Pagers): base(fm)
{
_path = path;
_filename = filename;
_chapters = chapters;
_parent = parent;
_fUtils = FUtils;
_pager = Pagers;
}
public override int Count
{
/* --- return chapter count --- */
get { return _chapters;}
}
public override Android.Support.V4.App.Fragment GetItem(int _position)
{
/* --- get specific item --- */
return new AwesomeFragment (_path, _filename, _position, _parent, _fUtils, _pager);
}
}
public class AwesomeFragment : Android.Support.V4.App.Fragment
{
string _path;
WebView web_view;
string _filename;
int _position;
Activity _parent;
BanjiChapter _chap;
FileUtilities _fUtils;
ViewPager _pager;
public AwesomeFragment () {}
public AwesomeFragment (string path,
string filename,
int position,
Activity parent,
FileUtilities FUtils,
ViewPager Pager)
{
_path = path;
_filename = filename;
_position = position;
_parent = parent;
_fUtils = FUtils;
_pager = Pager;
}
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
/* --- Create the view --- */
var view = inflater.Inflate (Resource.Layout.BookWebView, container, false);
//return the chapter
_chap = _fUtils.ReturnChapterDetails(_filename, _position);
web_view = view.FindViewById<WebView> (Resource.Id.webview);
web_view.SetWebViewClient(new BanjiWebClient(_position,_path,_parent, _pager, _chap.Name ));
web_view.SetBackgroundColor(Color.Transparent);
web_view.Settings.JavaScriptEnabled = true;
web_view.Tag = 300 + _position;
switch(Resources.DisplayMetrics.DensityDpi){
case Android.Util.DisplayMetricsDensity.Medium:
{
web_view.SetLayerType (LayerType.Software, null);
break;
}
case Android.Util.DisplayMetricsDensity.High:
{
web_view.SetLayerType (LayerType.Hardware, null);
break;
}
case Android.Util.DisplayMetricsDensity.Xhigh:
{
web_view.SetLayerType (LayerType.Hardware, null);
break;
}
}
if (_chap.Background == null) {
view.SetBackgroundColor (Color.White);
} else {
view.SetBackgroundDrawable (new BitmapDrawable (BitmapFactory.DecodeByteArray (_chap.Background, 0, _chap.Background.Length)));
}
if (_position == 0) {
web_view.LoadUrl ("file://" + _path + "/" + _chap.Name);
}
return view;
}
public BChapter GetCurrentBChapter()
{
return _chap;
}
}
EDIT:
BookView.axml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffececec">
<!-- The main content view -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/mainView">
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/bookView" />
<ImageButton
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="left|center"
android:background="#null"
android:id="#+id/menuButton"
/>
</FrameLayout>
<!-- The navigation drawer -->
<LinearLayout
android:id="#+id/left_menu"
android:layout_width="250dp"
android:layout_height="match_parent"
android:choiceMode="singleChoice"
android:layout_gravity="start"
android:divider="#android:color/transparent"
android:dividerHeight="0dp"
android:background="#111">
<Button
android:id="#+id/backStep"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="#style/button_text"
android:background="#ff000000"
android:fitsSystemWindows="false" />
<Space
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/screllArea">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/ThumbLayout" />
</ScrollView>
</LinearLayout>
</android.support.v4.widget.DrawerLayout>
BookWebView
<?xml version="1.0" encoding="utf-8"?>
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Use a FragmentStatePagerAdapter instead; it is designed to minimise memory overhead by possibly destroying the fragment when it is not visible, saving only the state information of that fragment.
From the developers docs:
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.
See here for documentation.
I'd like to have a TextView display text, and when you click/longclick on it, a textbox should "show up" and allow editing of said text. When you're done editing (onkey enter i suppose) it should revert back to a textview with the updated text...
I'm wondering if it's feasable to implement such a widget or should I hack a workaround? Tips and suggestions are very welcome.
If you need further idea of what I mean, just go to your e.g. (windows) skype profile and see for yourself.
EDIT:
Clarification: I'm specifically asking for a widget or such which is a textview until clicked on, then transforms to an edittext containing the same text; once done editing it transforms back to a textview representing the new changed text. Thats what i mean by "edittext on demand widget".
But I'm hoping to get something better than
public class Widget {
TextView text;
EditText edit;
String textToRepresent;
//...
}
You have a few different options here.
First you will have to register an onClick or onLongClick to the TextView that you want to make interactive. Just make sure that the user knows it's clickable
Then have your onClick function start a DialogFragment. I like to create show functions. Note that you can use the support libraries here to make your app backwards compatible.
private void showDialog() {
MyDialogFragment dialog = new MyDialogFragment();
dialog.show(getSupportFragmentManager(), "dialog");
}
The DialogFragment is pretty straight forward. In your onCreateView you'll inflate the View that you'll want to display to the user. You can alternatively wrap it with a simple AlertDialogBuilder if you don't want to go custom.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.your_dialog_layout);
mTitleEditText = (TextView) view.findViewById(R.id.title);
mTitleEditText.setOnClickListener(this);
return view;
}
After your findViewByIds set your onClickListeners.
The last thing you have to take care of is getting data back into your original TextView.
You can do this by creating a public method in your Activity that you can call from inside of your DialogFragment. Something like this
#Override
public void onClick(View v) {
int clickedId = v.getId();
if (clickedId == mDoneButton.getId()) {
MyActivity activity = (MyActivity)getActivity();
mTitle = mTitleEditText.getText().toString();
activity.setText(mTitle);
dismiss();
}
}
I would recommend using a DialogFragment because it will handle your life cycle nicely.
However, another option would be to create a new Activity themed to be a dialog
<activity android:theme="#android:style/Theme.Dialog" />
Then you can startActivityForResult to display your dialog and then capture your results in onActivityResult
Here is my solution. I just give you the basic one. Create a TextView in front of EditText and two Button OK,Cancel (You can change to ImageButton like Skype). Change the visiblity of two view. The code is so simple without comment. You can add some null checking according your logic.
public class CompoundTextView extends RelativeLayout implements OnClickListener {
private EditText edt;
private TextView txt;
RelativeLayout layout;
public SkypeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
edt = (EditText) findViewById(R.id.edt);
txt = (TextView) findViewById(R.id.txt_name);
layout = (RelativeLayout) findViewById(R.id.layout);
Button ok = (Button) findViewById(R.id.ok_btn);
Button cancel = (Button) findViewById(R.id.cancel_btn);
ok.setOnClickListener(this);
cancel.setOnClickListener(this);
txt.setOnClickListener(this);
}
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.ok_btn:
String editString = edt.getText().toString();
txt.setText(editString);
layout.setVisibility(View.INVISIBLE);
txt.setVisibility(View.VISIBLE);
break;
case R.id.cancel_btn:
layout.setVisibility(View.INVISIBLE);
txt.setVisibility(View.VISIBLE);
break;
case R.id.txt_name:
txt.setVisibility(View.INVISIBLE);
layout.setVisibility(View.VISIBLE);
break;
}
}
}
Create a XML skypetextview. You can customize font and background to make it's prettier.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="#+id/txt_name"
android:layout_width="fill_parent"
android:layout_height="100dp"
android:textColor="#FFFFFF"
android:textSize="14sp"
android:background="#ff0000" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
android:id="#+id/layout" >
<EditText
android:id="#+id/edt"
android:layout_width="270dp"
android:layout_height="100dp" />
<Button
android:id="#+id/ok_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#id/edt"
android:text="OK" />
<Button
android:id="#+id/cancel_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/ok_btn"
android:layout_toRightOf="#id/edt"
android:text="Cancel" />
</RelativeLayout>
</RelativeLayout>
add (or include) this view to the layout you want.
Example :
public class TestActivity extends Activity {
SkypeTextView test;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LayoutInflater inflate = getLayoutInflater();
test = (SkypeTextView ) inflate.inflate(R.layout.compound_text_view,
null);
setContentView(test);
}
PS: i forgot. You should add some underline format for your textview in order to make user notice it clickable
Let a EditText change its background based on its state(Editable or Frozen). Set a background selector that does this.
Use this selector xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_focused="true" android:drawable="#android:drawable/edit_text"/>
<item android:drawable="#android:drawable/screen_background_light_transparent"/>
</selector>
Like I said on thursday... Yul was pretty close but not quite close. He did have a general same idea but (theoretically) rushed into code too early ;)
The TextBoxOnDemand code supplied below is production-ready. The idea is similar to what I wanted to avoid in the OP and what Yul suggested, but with optimal implementation (using a ViewSwitcher instead of a RelativeLayout for instance)
I gathered the resources needed for this in the following articles:
Creating custom view from xml
Declaring a custom android UI element using XML
Defining custom attrs
How to pass custom component parameters in java and xml
http://kevindion.com/2011/01/custom-xml-attributes-for-android-widgets/
and decided to post them here because the official Google "training" docs are useless and are either obsolete (deprecated) or do not cover what I needed. I hope you don't mind me claiming my own bounty, but this is the solution I wanted (and expected, ergo the bounty).
I guess the code will have to do ;)
TextBoxOnDemand.java:
package com.skype.widget;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.text.SpannableString;
import android.text.style.UnderlineSpan;
import android.text.util.Linkify;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnHoverListener;
import android.view.View.OnLongClickListener;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.ViewSwitcher;
import com.skype.ref.R;
import com.skype.ref.RemoteKeys;
public class TextBoxOnDemand extends ViewSwitcher implements OnClickListener, OnLongClickListener, OnFocusChangeListener, OnHoverListener,
OnEditorActionListener
{
public static final String LOGTAG = "TextBoxOnDemand";
private View btmGuard;
private ImageButton cancel, accept;
private EditText editor;
private RelativeLayout editorLayout;
private TextView face;
private String hint = new String();
private boolean inEditMode = false; //normally this is in textview mode
private boolean inputReady = false;
private String ourData = new String();
private String prefillData = new String();
private String tag = new String(); //usually tag is empty.
private View topGuard;
private int autoLinkMask;// = Linkify.EMAIL_ADDRESSES; //Linkify.ALL;
private ColorStateList textColor, hintColor = null;
public TextBoxOnDemand(Context context)
{
super(context);
build(context);
setEditable(false); //init
}
public TextBoxOnDemand(Context context, AttributeSet attrs)
{
super(context, attrs);
build(context);
init(context, attrs);
setEditable(false); //init
}
public String getPrefillData()
{
return prefillData;
}
public String getTag()
{
return tag;
}
public String getText()
{
Log.d(LOGTAG, "getText() returning '" + ourData + "'");
return ourData;
}
public boolean hasPrefillData()
{
return prefillData.isEmpty();
}
public boolean isEditable()
{
Log.d(LOGTAG, "isEditable() returning " + inEditMode);
return inEditMode;
}
#Override
public void onClick(View v)
{
Log.d(LOGTAG, "onClick(" + v + ")");
if (inEditMode)
{
if (v.equals(accept))
{
if (editor.getEditableText().length() == 0 || editor.getEditableText().length() > 5)
ourData = editor.getEditableText().toString();
setEditable(false);
} else if (v.equals(cancel))
{
setEditable(false);
}
}
}
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event)
{
// Log.d(LOGTAG, "onEditorAction(" + v + ", " + actionId + ", " + event + ") fired!");
Log.d(LOGTAG, "onEditorAction() fired, inputReady = " + inputReady);
if (editor.getEditableText().length() > 0 && editor.getEditableText().length() < (prefillData.length() + 2)) return true; //the user needs to enter something
if (inputReady && (event.getKeyCode() == RemoteKeys.ENTER.keycode() || event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) //always is
{
if (editor.getEditableText().length() > prefillData.length() || editor.getEditableText().length() == 0)
ourData = editor.getEditableText().toString();
setEditable(false);
return false;
}
if ((editor.getEditableText().toString().compareToIgnoreCase(ourData) == 0 || editor.getEditableText().toString()
.compareToIgnoreCase(prefillData) == 0)
&& !inputReady) //means we didn't just keep on holding enter
return true;
else
inputReady = true;
return true;
}
#Override
public void onFocusChange(View v, boolean hasFocus)
{
Log.d(LOGTAG, "onFocusChange(" + v + ", " + hasFocus + ")\tinEditMode = " + inEditMode);
if (inEditMode)
{
if (hasFocus && (v.equals(topGuard) || v.equals(btmGuard)))
{
setEditable(false);
requestFocus();
}
if (hasFocus && (v.equals(editor) || v.equals(accept) || v.equals(cancel)))
{
//do nothing, you should be able to browse freely here
if (ourData.isEmpty() && editor.getEditableText().length() < prefillData.length())
{
Log.d(LOGTAG, "adding prefill, before = " + editor.getEditableText());
editor.setText("");
editor.append(prefillData);
Log.d(LOGTAG, "now is = " + editor.getEditableText());
}
}
} else
{
String text = (ourData.isEmpty()) ? hint : ourData;
ColorStateList color;
if (hintColor != null && ourData.isEmpty())
color = hintColor;
else
color = textColor;
face.setTextColor(color);
if (hasFocus)
{
SpannableString ss = new SpannableString(text);
ss.setSpan(new UnderlineSpan(), 0, text.length(), 0);
face.setText(ss);
} else
face.setText(text);
}
}
#Override
public boolean onHover(View v, MotionEvent event)
{
// Log.d(LOGTAG, "onHover()");
String text = (ourData.isEmpty()) ? hint : ourData;
ColorStateList color;
if (hintColor != null && ourData.isEmpty())
color = hintColor;
else
color = textColor;
face.setTextColor(color);
switch (event.getAction())
{
case MotionEvent.ACTION_HOVER_ENTER:
SpannableString ss = new SpannableString(text);
ss.setSpan(new UnderlineSpan(), 0, text.length(), 0);
face.setText(ss);
break;
case MotionEvent.ACTION_HOVER_EXIT:
face.setText(text);
break;
}
return true;
}
#Override
public boolean onLongClick(View v)
{
Log.d(LOGTAG, "onLongClick()\tinEditMode = " + inEditMode);
if (!inEditMode) //implies that getDisplayedChild() == 0, meaning the textview
{
setEditable(true);
return true;
} else
return false;
}
public void setEditable(boolean value)
{
Log.d(LOGTAG, "setEditable(" + value + ")");
inEditMode = value;
if (inEditMode)
{
//display the editorLayout
face.setOnLongClickListener(null);
face.setOnHoverListener(null);
face.setOnFocusChangeListener(null); //because of GC.
face.setOnClickListener(null);
face.setVisibility(View.GONE);
setDisplayedChild(1);
editorLayout.setVisibility(View.VISIBLE);
editor.setOnFocusChangeListener(this);
editor.setOnEditorActionListener(this);
cancel.setOnClickListener(this);
accept.setOnClickListener(this);
accept.setOnFocusChangeListener(this);
cancel.setOnFocusChangeListener(this);
} else
{
editor.setOnFocusChangeListener(null);
editor.setOnEditorActionListener(null);
cancel.setOnClickListener(null);
accept.setOnClickListener(null);
accept.setOnFocusChangeListener(null);
cancel.setOnFocusChangeListener(null);
editorLayout.setVisibility(View.GONE);
setDisplayedChild(0);
face.setVisibility(View.VISIBLE);
face.setOnLongClickListener(this);
face.setOnHoverListener(this);
face.setOnFocusChangeListener(this);
face.setOnClickListener(this);
face.setFocusable(true);
face.setFocusableInTouchMode(true);
}
updateViews();
}
#Override
public void setNextFocusDownId(int nextFocusDownId)
{
super.setNextFocusDownId(nextFocusDownId);
face.setNextFocusDownId(nextFocusDownId);
// editor.setNextFocusDownId(nextFocusDownId);
accept.setNextFocusDownId(nextFocusDownId);
cancel.setNextFocusDownId(nextFocusDownId);
}
#Override
public void setNextFocusForwardId(int nextFocusForwardId)
{
super.setNextFocusForwardId(nextFocusForwardId);
face.setNextFocusForwardId(nextFocusForwardId);
editor.setNextFocusForwardId(nextFocusForwardId);
}
#Override
public void setNextFocusLeftId(int nextFocusLeftId)
{
super.setNextFocusLeftId(nextFocusLeftId);
face.setNextFocusLeftId(nextFocusLeftId);
editor.setNextFocusLeftId(nextFocusLeftId);
}
#Override
public void setNextFocusRightId(int nextFocusRightId)
{
super.setNextFocusRightId(nextFocusRightId);
face.setNextFocusRightId(nextFocusRightId);
cancel.setNextFocusRightId(nextFocusRightId);
}
#Override
public void setNextFocusUpId(int nextFocusUpId)
{
super.setNextFocusUpId(nextFocusUpId);
face.setNextFocusUpId(nextFocusUpId);
// editor.setNextFocusUpId(nextFocusUpId);
accept.setNextFocusUpId(nextFocusUpId);
cancel.setNextFocusUpId(nextFocusUpId);
}
public void setPrefillData(String prefillData)
{
this.prefillData = new String(prefillData);
}
public String setTag()
{
return tag;
}
public void setText(String text)
{
Log.d(LOGTAG, "setText(" + text + ")");
ourData = text;
updateViews();
}
private void build(Context context)
{
Log.d(LOGTAG, "build()");
addView(View.inflate(context, R.layout.textboxondemand, null));
setFocusable(true);
setFocusableInTouchMode(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setOnFocusChangeListener(this);
setOnLongClickListener(this);
face = (TextView) findViewById(R.id.TBOD_textview);
editorLayout = (RelativeLayout) findViewById(R.id.TBOD_layout);
editor = (EditText) findViewById(R.id.TBOD_edittext);
accept = (ImageButton) findViewById(R.id.TBOD_accept);
cancel = (ImageButton) findViewById(R.id.TBOD_cancel);
topGuard = (View) findViewById(R.id.TBOD_top);
btmGuard = (View) findViewById(R.id.TBOD_bottom);
face.setFocusable(true);
face.setFocusableInTouchMode(true);
face.setOnLongClickListener(this);
face.setOnHoverListener(this);
face.setOnFocusChangeListener(this);
face.setOnClickListener(this);
editor.setOnFocusChangeListener(this);
editor.setOnEditorActionListener(this);
editor.setHint(hint);
editor.setFocusable(true);
editor.setFocusableInTouchMode(true);
accept.setOnClickListener(this);
accept.setOnFocusChangeListener(this);
accept.setFocusable(true);
cancel.setFocusable(true);
cancel.setOnFocusChangeListener(this);
cancel.setOnClickListener(this);
topGuard.setFocusable(true);
topGuard.setOnFocusChangeListener(this);
btmGuard.setFocusable(true);
btmGuard.setOnFocusChangeListener(this);
editor.setNextFocusRightId(R.id.TBOD_accept);
editor.setNextFocusDownId(R.id.TBOD_bottom);
editor.setNextFocusUpId(R.id.TBOD_top);
accept.setNextFocusLeftId(R.id.TBOD_edittext);
accept.setNextFocusRightId(R.id.TBOD_cancel);
cancel.setNextFocusLeftId(R.id.TBOD_accept);
}
private void init(Context context, AttributeSet attrs)
{
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextBoxOnDemand);
//Use a
Log.d(LOGTAG, "init()");
if (a == null) Log.d(LOGTAG, "Did you include 'xmlns:app=\"http://schemas.android.com/apk/res-auto\"' in your root layout?");
final int N = a.getIndexCount();
for (int i = 0; i < N; ++i)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.TextBoxOnDemand_android_hint:
hint = new String(a.getString(attr));
editor.setHint(a.getString(attr));
break;
case R.styleable.TextBoxOnDemand_android_text:
ourData = new String(a.getString(attr));
break;
case R.styleable.TextBoxOnDemand_android_inputType:
int inputType = a.getInt(attr, -1);
if (inputType != -1) editor.setInputType(inputType);
break;
case R.styleable.TextBoxOnDemand_android_textColor:
textColor = a.getColorStateList(attr);
face.setTextColor(textColor);
break;
case R.styleable.TextBoxOnDemand_android_linksClickable:
face.setLinksClickable(a.getBoolean(attr, true));
break;
case R.styleable.TextBoxOnDemand_android_textColorHint:
hintColor = a.getColorStateList(attr);
break;
case R.styleable.TextBoxOnDemand_android_autoLink:
autoLinkMask = a.getInt(attr, 0);
face.setAutoLinkMask(autoLinkMask);
break;
default:
Log.d(LOGTAG, "Skipping attribute " + attr);
}
}
//Don't forget this
a.recycle();
}
private void updateViews()
{
Log.d(LOGTAG, "updateViews()");
// if (getDisplayedChild() == 0) //first child - textview
if (!inEditMode) //first child - textview
{
if (ourData.isEmpty())
{
if (hintColor != null) face.setTextColor(hintColor);
face.setText(hint);
} else
{
face.setTextColor(textColor);
face.setText(ourData);
}
face.setFocusable(true);
face.setFocusableInTouchMode(true);
face.setAutoLinkMask(autoLinkMask);
} else
{ //second child - edittext
editor.setFocusable(true);
editor.setFocusableInTouchMode(true);
if (ourData.startsWith(prefillData) || ourData.length() >= prefillData.length())
editor.setText("");
else
editor.setText(prefillData);
editor.append(ourData);
inputReady = false;
editor.requestFocus();
}
}
public void setAutoLinkMask(LinkifyEnum linkifyEnumConstant)
{
switch (linkifyEnumConstant)
{
case ALL:
autoLinkMask = Linkify.ALL;
break;
case EMAIL_ADDRESSES:
autoLinkMask = Linkify.EMAIL_ADDRESSES;
break;
case MAP_ADDRESSES:
autoLinkMask = Linkify.MAP_ADDRESSES;
break;
case PHONE_NUMBERS:
autoLinkMask = Linkify.PHONE_NUMBERS;
break;
case WEB_URLS:
autoLinkMask = Linkify.WEB_URLS;
break;
case NONE:
default:
autoLinkMask = 0;
break;
}
//set it now
face.setAutoLinkMask(autoLinkMask);
}
public enum LinkifyEnum
{
ALL, EMAIL_ADDRESSES, MAP_ADDRESSES, PHONE_NUMBERS, WEB_URLS, NONE
};
}
I'm still working out some focus-related issues but this works as intended. When I use onFocuslistener 1, you can't focus from one TextBox to the other; when the textbox itself is focusable, I can focus from one to the other just fine, but I cannot inter-focus thru children and thus can't focus on the edittext to type.
the XML file:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
android:id="#+id/TBOD_textview"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:autoLink="email"
android:focusable="true"
android:focusableInTouchMode="true"
android:linksClickable="true"
android:textAppearance="?android:attr/textAppearanceMedium" />
<RelativeLayout
android:id="#+id/TBOD_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<EditText
android:id="#+id/TBOD_edittext"
android:layout_width="300dp"
android:layout_height="30dp"
android:layout_below="#+id/TBOD_textview"
android:focusable="true"
android:focusableInTouchMode="true"
android:imeOptions="actionDone"
android:inputType="none"
android:maxLines="1"
android:padding="2dp"
android:singleLine="true"
android:textColor="#android:color/black"
android:textSize="14dp" />
<ImageButton
android:id="#+id/TBOD_accept"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="#+id/TBOD_edittext"
android:layout_marginLeft="15dp"
android:layout_toRightOf="#+id/TBOD_edittext"
android:background="#drawable/button_accept_selector" />
<ImageButton
android:id="#+id/TBOD_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="#+id/TBOD_edittext"
android:layout_marginLeft="5dp"
android:layout_toRightOf="#+id/TBOD_accept"
android:background="#drawable/button_cancel_selector" />
<View
android:id="#+id/TBOD_top"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_alignParentTop="true"
android:background="#android:color/transparent" />
<View
android:id="#+id/TBOD_bottom"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_alignParentBottom="true"
android:background="#android:color/transparent" />
</RelativeLayout>
</RelativeLayout>
and finally, the attrs.xml file:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TextBoxOnDemand">
<attr name="android:text" />
<attr name="android:inputType" />
<attr name="android:hint" />
<attr name="android:textColor" />
<attr name="android:textColorHint" />
<attr name="android:linksClickable" />
<attr name="android:autoLink" />
</declare-styleable>
</resources>
This is how I used it in my main xml (after including the required namespace add):
<com.shark.widget.TextBoxOnDemand
android:id="#+id/profile_email2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="#+id/profile_skypename"
android:layout_below="#+id/profile_email_placeholder"
android:hint="#string/add_email"
android:inputType="textEmailAddress"
android:textColor="#android:color/white"
android:textColorHint="#color/skype_blue" />
EDIT: I've debugged the focus issues. It turns out that giving focus to children is difficult unless you call
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Which kinda remedies the issue but still doesn't solve it. After some while of playing around with the onFocusChange() listener still trying to get the perfect behaviour, I threw in the towel and put in added two focus guards. I realized I cannot track the loss of focus only on my container (due to it never receiving focus) but I might as well track the idea of wanting to move away from the edit field... So i went the dirty route and added two invisible bar-like views to sandwitch the edittext in between. Once they got the focus, I could hide the component and ensure they transition properly.
And there it is, now it works as it should. Thanks to all who participated.
EDIT3: final polished version, i dumped the custom tags because they simply don't work reliably enough. Lesson to be learned: if there is an android tag for something, don't bother cloning it.
I want to get user input for my OpenGL ES 2.0 application, but there are 2 problems:
1) How can I bring the software keyboard to the front of my app?
2) How can I catch input from it?
I tried to use this:
//OpenGL ES 2.0 view class
public class OGLES2View extends GLSurfaceView
{
private static final int OGLES_VERSION = 2;
private static Handler softKeyboardHandler;
private final static int SHOW_IME_KEYBOARD = 0;
private final static int HIDE_IME_KEYBOARD = 1;
private static EditText textEdit;
private static InputMethodManager imm;
private void setSoftKeyboardHandler()
{
softKeyboardHandler = new Handler()
{
public void handleMessage(Message msg)
{
switch(msg.what)
{
case SHOW_IME_KEYBOARD:
textEdit.requestFocus();
imm.showSoftInput(textEdit,inputMethodManager.SHOW_IMPLICIT);//Nothing happens
Log.i("GLVIEW","SHOW KEYBOARD");
break;
case HIDE_IME_KEYBOARD:
imm.hideSoftInput(textEdit, 0);
Log.i("GLVIEW","HIDE KEYBOARD");
break;
default:
break;
}
}
};
}
public OGLES2View(Context context)
{
super(context);
textEdit = new EditText(context);
setEGLContextClientVersion(OGLES_VERSION);
setRenderer(new OGLES2Renderer());
imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
setSoftKeyboardHandler();
}
public static void showIMEKeyboard()
{
softKeyboardHandler.sendEmptyMessage(SHOW_IME_KEYBOARD);
}
public static void hideIMEKeyboard()
{
softKeyboardHandler.sendEmptyMessage(HIDE_IME_KEYBOARD);
}
//In main activity class
private GLSurfaceView ogles2SurfaceView = null;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//...
ogles2SurfaceView = new OGLES2View(this);
setContentView(ogles2SurfaceView);
}
Handler gets messages, but I got no software keyboard.
To catch text, I wrote some class:
public class TextInputWatcher implements TextWatcher
and:
textEdit.addTextChangedListener(/*TextInputWatcher instance*/);
Or extend a TextEdit so it catches inputed text on back or enter key.
P.S. I got a tablet - transformer, so there is a hardware keyboard attached. I tried with it and without, but no difference. So bonus question - if there is a hardware keyboard will it prevent a software keyboard from popping up and how can input be gotten from it then?.
Show keyboard:
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
Hide keyboard:
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
I made 2d game. I think you have the same problem like me before. Try this:
class DrawingPanel extends SurfaceView implements SurfaceHolder.Callback {
private static DrawThread _thread;
public DrawingPanel(Context context, AttributeSet attrs) {
super(context, attrs);
getHolder().addCallback(this);
_thread = new DrawThread(getHolder(), this);
}
....
Layout 'gameview':
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<!-- YOUR SURFACE -->
<com.yourcompany.DrawingPanel android:id="#+id/surfaceView" android:layout_width="fill_parent"
android:layout_height="fill_parent"></com.yourcompany.DrawingPanel>
<!-- YOUR BUTTONS -->
<RelativeLayout android:id="#+id/controlPanel" android:layout_width="fill_parent" android:orientation="horizontal"
android:layout_height="fill_parent" >
<RelativeLayout android:layout_width="50px" android:orientation="vertical"
android:layout_height="fill_parent" android:gravity="center_vertical" android:layout_alignParentLeft="true">
<Button android:id="#+id/leftButton" android:layout_width="wrap_content"
android:layout_height="50px" android:background="#xml/button_left_state"/>
<Button android:id="#+id/upgradeButton" android:layout_width="wrap_content"
android:layout_below="#id/leftButton"
android:layout_height="50px" android:background="#xml/button_upgrade_state"/>
</RelativeLayout>
</RelativeLayout>
</FrameLayout>
Then you should set content in game activity like below:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gameview);
...
Hope It helps you.