JList lazy load images - swing

since i am not a java swing expert i need some help to understand why my images in my JList do not appear.
I have a JList that pops up containing all products (with inline pictures) while the user enters a search criteria. The results come from lucene and will be rendered in a JList in real time.
To lazy load the inline product images i am using a swingworker inside my rendering class.
Any help would be great!
public abstract class MatchRenderer implements ListCellRenderer {
#Override
public Component getListCellRendererComponent(JList list, final Object value, int index,
boolean isSelected, boolean cellHasFocus) {
Component component = defaultRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (quickRenderMode) {
return component;
} else {
try {
component = renderHook(value, component);
} catch (Exception e) {
System.err.println("Search string: " + searchString);
System.err.println(value.toString());
e.printStackTrace();
}
JPanel itemPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
JLabel label = new JLabel(defaultIcon, SwingConstants.HORIZONTAL);
itemPanel.add(label);
itemPanel.add(component);
if (value instanceof QoogleEntity && ((QoogleEntity) value).isProduct()) {
QoogleEntity qoogleItem = (QoogleEntity) value;
String imageUrl = qoogleItem.getQInfos().get(0).getqValue();
//LAZY LOAD STARTS HERE...
new ImageRetriever(label, imageUrl).execute();
}
return itemPanel;
}
}
protected abstract Component renderHook(Object value, Component component);
class ImageRetriever extends SwingWorker<ImageIcon, String> {
private JLabel lbImage;
private String imageUrl;
public ImageRetriever(JLabel lbImage, String imageUrl) {
this.lbImage = lbImage;
this.imageUrl = imageUrl;
}
#Override
protected void done() {
try {
lbImage.setIcon(get());
lbImage.repaint();
} catch (Exception e) {
}
}
#Override
protected ImageIcon doInBackground() throws Exception {
return ImageLoader.loadImageFromUrl(imageUrl, 80, 80);
}
};

Related

How to correctly handle data management with SharedPreferences?

Right now, I am in the process of "optimizing" my app. I am still a beginner, so what I am doing is basically moving methods from my MainActivity.class to their separate class. I believe it's called Encapsulation (Please correct me if I'm wrong).
My application needs to :
Get a YouTube Playlist Link from the YouTube App (with an Intent, android.intent.action.SEND).
Use the link to fetch data from the Google Servers with the YouTubeApi and Volley.
Read the data received and add it to an arrayList<String>.
What my YouTubeUsage.java class is supposed to do, is fetch data with the YouTubeApi and Volley then store the data using SharedPreferences. Once the data is saved, the data is being read in my ConvertActivity.class (It's an activity specifically created for android.intent.action.SEND) with my method getVideoIds() before setting an adapter for my listView in my createRecyclerView() method.
YouTubeUsage.java
public class YoutubeUsage {
private Boolean results = false;
private String mResponse;
private ArrayList<String> videoIds = new ArrayList<>();
String Url;
public String getUrl(String signal) {
String playlistId = signal.substring(signal.indexOf("=") + 1);
this.Url = "https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails%2C%20snippet%2C%20id&playlistId=" +
playlistId + "&maxResults=25&key=" + "API_KEY";
return this.Url;
}
public void fetch(String Url, final Context context){
RequestQueue queue = Volley.newRequestQueue(context);
StringRequest request = new StringRequest(Request.Method.GET, Url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
sharedPreferences(response, context);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("VolleyError", Objects.requireNonNull(error.getMessage()));
}
});
queue.add(request);
}
private void sharedPreferences(String response, Context context){
SharedPreferences m = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = m.edit();
if (m.contains("serverResponse")){
if (!m.getString("serverResponse", "").equals(response)){
editor.remove("serverResponse");
editor.apply();
updateSharedPreferences(response, context);
}
} else{
updateSharedPreferences(response, context);
}
}
private void updateSharedPreferences(String mResponse, Context mContext){
SharedPreferences m = PreferenceManager.getDefaultSharedPreferences(mContext);
SharedPreferences.Editor editor = m.edit();
editor.putString("serverResponse", mResponse);
editor.apply();
}
}
ConvertActivity.java
public class ConvertActivity extends AppCompatActivity {
YoutubeUsage youtubeUsage = new YoutubeUsage();
ArrayList<String> videoIDs = new ArrayList<>();
String Url = "";
ListView listView;
MyCustomAdapter myCustomAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_convert);
listView = findViewById(R.id.listview_convert);
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
Url = youtubeUsage.getUrl(Objects.requireNonNull(intent.getStringExtra("android.intent.extra.TEXT")));
}
//I would like to avoid the try/catch below
try {
videoIDs = getVideoIDs(Url, this);
createRecyclerView(videoIDs);
Log.i("ResponseVideoIDs", String.valueOf(videoIDs.size()));
} catch (JSONException e) {
e.printStackTrace();
}
}
private ArrayList<String> getVideoIDs(String Url, Context context) throws JSONException {
ArrayList<String> rawVideoIDs = new ArrayList<>();
youtubeUsage.fetch(Url, context);
SharedPreferences m = PreferenceManager.getDefaultSharedPreferences(context);
String serverResponse = m.getString("serverResponse", "");
JSONObject jsonObject = new JSONObject(serverResponse);
JSONArray jsonArray = jsonObject.getJSONArray("items");
for (int i = 0; i<jsonArray.length(); i++){
JSONObject jsonObject1 = jsonArray.getJSONObject(i);
JSONObject jsonVideoId = jsonObject1.getJSONObject("contentDetails");
rawVideoIDs.add(jsonVideoId.getString("videoId"));
}
return rawVideoIDs;
}
private void createRecyclerView(ArrayList<String> videoIDs){
myCustomAdapter = new MyCustomAdapter(this, videoIDs);
listView.setAdapter(myCustomAdapter);
myCustomAdapter.notifyDataSetChanged();
}
}
Everything works fine, however, my sharedPreferences never gets updated. Which means, if I share a YouTube playlist from the YouTube App to my app with 3 items in it, it will work fine. The Listview will show 3 items with their corresponding IDs as it should. But, if I share a YouTube playlist again, my app will still hold on to the data of the previous playlist I shared (even if I close it), showing the item number and the IDs of the previous link. If i continue to share the same playlist over and over, it will eventually show the correct number of items and the correct IDs.
I could totally put all my methods from the YouTubeUsage.java in my ConvertActivity.class preventing me from using SharedPreferences to transfer data between the two java classes. However, JSON throws an exception. That means I have to encapsulate my code with try/catch. I would like to avoid those since I need to do a lot of operations on the data just received by Volley (check a class size, look for certains strings). I find that doing this in these try/catch don't work like I want. (i.e. outside the try/catch, the values remains the same even if I updated them in the try/catch).
I want to know two things.
How can I correct this problem?
Is this the most efficient way to do this (optimization)? (I though of maybe
converting the VolleyResponse to a string with Gson then store the String file, but I don't know if that's the best way to do it since it's supposed to be
provisional data. It feels like just more of the same).
Thank You!
There is an issue with making assumptions about order of events. Volley will handle requests asynchronously, so it is advisable to implement the observer pattern here.
Create a new Java file that just contains:
interface MyNetworkResponse {
void goodResponse(String responseString);
}
Then make sure ConvertActivity implements MyNetworkResponse and create method:
void goodResponse(String responseString) {
// handle a positive response here, i.e. extract the JSON and send to your RecyclerView.
}
within your Activity.
In your YoutubeUsage constructor, pass in the Activity context (YoutubeUsage) and then store this in a YoutubeUsage instance variable called ctx.
In onCreate, create an instance of YoutubeUsage and pass in this.
In onResponse just call ctx.goodResponse(response).
Amend the following block to:
if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
Url = youtubeUsage.getUrl(Objects.requireNonNull(intent.getStringExtra("android.intent.extra.TEXT")));
youtubeUsage.fetch(Url);
}
Delete the try/catch from onCreate.
And no need to use SharedPreferences at all.
UPDATE
Try this code:
MyNetworkResponse.java
interface MyNetworkResponse {
void goodResponse(String responseString);
void badResponse(VolleyError error);
}
YoutubeUsage.java
class YoutubeUsage {
private RequestQueue queue;
private MyNetworkResponse callback;
YoutubeUsage(Object caller) {
this.callback = (MyNetworkResponse) caller;
queue = Volley.newRequestQueue((Context) caller);
}
static String getUrl(String signal) {
String playlistId = signal.substring(signal.indexOf("=") + 1);
return "https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails%2C%20snippet%2C%20id&playlistId=" + playlistId + "&maxResults=25&key=" + "API_KEY";
}
void fetch(String url){
StringRequest request = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
callback.goodResponse(response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
callback.badResponse(error);
}
});
queue.add(request);
}
}
ConvertActivity.java
public class ConvertActivity extends AppCompatActivity implements MyNetworkResponse {
YoutubeUsage youtubeUsage;
ArrayList<String> videoIDs = new ArrayList<>();
ListView listView;
MyCustomAdapter myCustomAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_convert);
listView = findViewById(R.id.listview_convert);
youtubeUsage = new YoutubeUsage(this);
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
String url = YoutubeUsage.getUrl(Objects.requireNonNull(intent.getStringExtra("android.intent.extra.TEXT")));
youtubeUsage.fetch(url);
}
}
private ArrayList<String> getVideoIDs(String serverResponse) throws JSONException {
ArrayList<String> rawVideoIDs = new ArrayList<>();
JSONObject jsonObject = new JSONObject(serverResponse);
JSONArray jsonArray = jsonObject.getJSONArray("items");
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject1 = jsonArray.getJSONObject(i);
JSONObject jsonVideoId = jsonObject1.getJSONObject("contentDetails");
rawVideoIDs.add(jsonVideoId.getString("videoId"));
}
return rawVideoIDs;
}
private void createRecyclerView(ArrayList<String> videoIDs) {
myCustomAdapter = new MyCustomAdapter(this, videoIDs);
listView.setAdapter(myCustomAdapter);
myCustomAdapter.notifyDataSetChanged();
}
#Override
public void goodResponse(String responseString) {
Log.d("Convert:goodResp", "[" + responseString + "]");
try {
ArrayList<String> rawVideoIDs = getVideoIDs(responseString);
createRecyclerView(rawVideoIDs);
} catch (JSONException e) {
// handle JSONException, e.g. malformed response from server.
}
}
#Override
public void badResponse(VolleyError error) {
// handle unwanted server response.
}
}

Search Result in new Window (JavaFx)

I am currently working on a software module consisting of searching in a database using JavaFx.
Everything is working as expected.
But the only problem is that in my result table I am showing only few details of search (from UX issues: I have too much details and long texts).
What I would like to do is to show a new window with all details of a clicked row in TextFields and TextArea.
I looked at several tutorials and answers, but unfortunately nothing is working.
Any help would be appreciated!
Thank you.
SearchResult.setOnMouseClicked(new EventHandler<MouseEvent>() {
Gemo temp;
#Override
public void handle(MouseEvent event) {
Gemo row = SearchResult.getSelectionModel().getSelectedItem();
if(row == null) {
System.out.print("I am not in");
return ;
}
else {
temp = row;
String id = String.format(temp.getRef());
System.out.println(id);
FXMLLoader loader=new FXMLLoader();
loader.setLocation(getClass().getResource("DetailsWindow.fxml"));
ControllerDetails gemodetails=loader.getController();
ControllerDetails gd=new ControllerDetails();
gd.SearchById(id);
try{
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
Parent p= loader.getRoot();
Stage stage=new Stage();
stage.setTitle("More Details");
stage.setScene(new Scene(p));
stage.show();
}
}
});
public class ControllerDetails {
#FXML
private TextField fn_patient;
#FXML
private TextField ln_patient;
#FXML
private TextField db_patient;
#FXML
private TextField id_patient;
#FXML
private TextField id_visit;
#FXML
private TextField date_visit;
#FXML
private TextField fn_user;
#FXML
private TextField ln_user;
#FXML
private TextField status;
#FXML
private TextArea com;
#FXML
public void initialize(){
}
public void SearchById(String id) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet rs = null;
try {
connection = ConnectionConfiguration.getConnection();
statement = connection.prepareStatement("my_sql_query");
rs = statement.executeQuery();
while (rs.next()) {
id_visit.setText(rs.getString(1));
id_patient.setText(rs.getString(2));
date_visit.setText(rs.getString(3));
com.setText(rs.getString(4));
fn_patient.setText(rs.getString(5));
ln_patient.setText(rs.getString(6));
db_patient.setText(rs.getString(7));
fn_user.setText(rs.getString(8));
ln_user.setText(rs.getString(9));
status.setText(rs.getString(10));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Try to create a simple fxml file with a listView. Place the data you are interested in into this listView. If you prefer something else than listView, that's ok too.
To open such fxml in a new window try something like that:
Stage s = new Stage();
try {
s.setScene(new Scene(new FXMLLoader(getClass().getResource("your_fxml_name")).load()));
s.show();
} catch (IOException e) {
e.printStackTrace();
}
You have two action options: either extract all the data and paste it into a table, or extract a small part that you put in a table, and extract the extra on demand (display details).
The example I give is not addicted to the approach - it simply tells how to transfer table data (a selected row) to a dialog (its controller)
public class Controller {
#FXML
private TableView<MySearchResult> tableView;
#FXML
private void initialize() {
tableView.setItems(new Database().serach("query", "query"));
tableView.setOnMouseClicked(event -> {
if(event.getClickCount() == 2) {
showDetailsDialog();
}
});
}
private void showDetailsDialog() {
MySearchResult result = tableView.getSelectionModel().getSelectedItem();
if(result == null) {
return;
}
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("details_dilaog.fxml"));
Dialog dlg = new Dialog();
dlg.initOwner(tableView.getScene().getWindow());
dlg.initModality(Modality.APPLICATION_MODAL);
dlg.getDialogPane().setContent((Parent) loader.load());
dlg.getDialogPane().getButtonTypes().setAll(ButtonType.OK);
DetailsDialogControler ddc = loader.getController();
ddc.showDetailsFor(result);
dlg.showAndWait();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
My goal is to show the logic in the showDetailsDialog() function.

Why is this <p:selectManyMenu> not working? (getAllDatasourceGroups() is not even called once)

My problem is about this primefaces tag:
<p:selectManyCheckbox id="datasourceGroup" value="#{sessionBean.datasourceGroups}" converter="datasourceGroupConverter">
<f:selectItems value="#{sesionBean.getAllDatasourceGroups()}" var="group" itemLabel="#{group.toString()}" itemValue="#{group}" />
</p:selectManyCheckbox>
It does not render any visible output (checkboxes) at all. From logging output i know that the 'sessionBean.getAllDatasourceGroups()' method is not even called once during page refresh. only the 'sessionBean.getDatasourcegroups()' getter for the 'datasourceGroups' property is called once.
And i can't figure out what the problem is. I have very similar usecases of <p:selectManyMenu> and <p:selectOneMenu> on the same page and they work fine. So i have a basic understanding of how this works...or so i thought :-)
here are the other relevant parts of the code for reference:
SessionBean:
#ManagedBean
#SessionScoped
public class SessionBean implements Serializable {
private List<DatasourceGroup> datasourceGroups = new ArrayList<>();
public List<DatasourceGroup> getDatasourceGroups() {
return datasourceGroups;
}
public void setDatasourceGroups(List<DatasourceGroup> datasourceGroups) {
this.datasourceGroups = datasourceGroups;
}
public List<DatasourceGroup> getAllDatasourceGroups() {
List<DatasourceGroup> list = Arrays.asList(DatasourceGroup.values());
return list;
}
}
DatasourceGroup Enum:
public enum DatasourceGroup {
KUNDEN (Permission.ZugriffKunden),
INKASSO (Permission.ZugriffInkasso),
INTERESSENTEN (Permission.ZugriffInteressenten),
WARN (Permission.ZugriffWarnadressen);
private Permission permissionNeeded;
DatasourceGroup(Permission permission) {
this.permissionNeeded=permission;
}
public Permission getPermissionNeeded() {
return permissionNeeded;
}
}
And the DatasourceGroupConverter:
#FacesConverter("datasourceGroupConverter")
public class DatasourceGroupConverter implements Converter {
#Override
public Object getAsObject(FacesContext fc, UIComponent uic, String value) {
if (Toolbox.isNullOrEmpty(value))
return null;
try {
return DatasourceGroup.valueOf(value);
} catch (IllegalArgumentException e) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Conversion Error:",
"'" + value + "' is not a valid datasource group name"));
}
}
#Override
public String getAsString(FacesContext fc, UIComponent uic, Object object) {
if(object != null && object instanceof DatasourceGroup) {
return ((DatasourceGroup)object).toString();
}
return null;
}
}
I'm using primefaces 6.0 by the way.

JavaFX, SceneBuilder, Populating TableView with MySQL Result Set

I have finally overcome my issue with a NPE in my code whilst learning FX/FXML. I now however have a different problem, a window opens with my TableView however there is no content in the table at all. As you cann I have printed out the JobList to make sure there is content being returned, and this returns three jobs (the correct amount). Am I missing something that binds the table to the returned list?
Here is the code;
public class SecondInterface implements Initializable {
private JobDataAccessor jAccessor;
private String aQuery = "SELECT * FROM progdb.adamJobs";
private Parent layout;
private Connection connection;
#FXML
TableView<Job> tView;
public void newI(Connection connection) throws Exception {
Stage primaryStage;
primaryStage = MainApp.primaryStage;
this.connection = connection;
System.out.println(connection);
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Test1.fxml"));
fxmlLoader.setController(this);
try {
layout = (Parent) fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
primaryStage.getScene().setRoot(layout);
}
public Parent getLayout() {
return layout;
}
#Override
public void initialize(URL url, ResourceBundle rb) {
jAccessor = new JobDataAccessor();
try {
System.out.println("This connection: " + connection);
System.out.println("This query: " + aQuery);
List<Job> jList = jAccessor.getJobList(connection, aQuery);
for (Job j : jList) {
System.out.println(j);
}
tView.getItems().addAll(jAccessor.getJobList(connection, aQuery));
} catch (SQLException e) {
e.printStackTrace();
}
}
}

How to know what text has been deleted from a JTextPane

I have added a document listener to a JTextPane. I want to know what text has been added or removed so I can take action if certain key words are entered. The insert part works just fine, but I do not know how to detect what text was deleted.
The insert works because the text is there and I can select it, but the delete has already removed the text so I get bad location exceptions sometimes.
I want to make reserved words that are not inside quotes bold so I need to know what has been removed, removing even one character (like a quote) could have a huge impact.
My code follows:
#Override
public void insertUpdate(DocumentEvent e)
{
Document doc = e.getDocument();
String i = "";
try
{
i = doc.getText(e.getOffset(), e.getLength());
}
catch(BadLocationException e1)
{
e1.printStackTrace();
}
System.out.println("INSERT:" + e + ":" + i);
}
#Override
public void removeUpdate(DocumentEvent e)
{
Document doc = e.getDocument();
String i = "";
try
{
i = doc.getText(e.getOffset(), e.getLength());
}
catch(BadLocationException e1)
{
e1.printStackTrace();
}
System.out.println("REMOVE:" + e + ":" + i);
}
This is strange that there is no simple way to get this information.
I've looked at the source code of Swing libraries for this. Of course - there is this information in DocumentEvent, which is of class AbstractDocument$DefaultDocumentEvent, which contains protected Vector<UndoableEdit> edits, which contains one element of type GapContent$RemoveUndo, which contains protected String string that is used only in this class (no other "package" classes get this) and this RemoveUndo class have no getter for this field.
Even toString didn't show it (because RemoveUndo hasn't overrided toString method):
[javax.swing.text.GapContent$RemoveUndo#6303ddfd hasBeenDone: true alive: true]
This is so strange for me that I belive that there is some other easy way to get the removed string and that I just don't know how to accomplish it.
One thing you can do is the most obvious:
final JTextArea textArea = new JTextArea();
textArea.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
previousText = textArea.getText();
}
});
textArea.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void removeUpdate(DocumentEvent e) {
if(previousText != null) {
String removedStr = previousText.substring(e.getOffset(), e.getOffset() + e.getLength());
System.out.println(removedStr);
}
}
#Override
public void insertUpdate(DocumentEvent e) {
}
#Override
public void changedUpdate(DocumentEvent e) {
}
});
where previousText is an instance variable.
or (the most nasty ever):
textArea.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void removeUpdate(DocumentEvent e) {
String removedString = getRemovedString(e);
System.out.println(removedString);
}
#Override
public void insertUpdate(DocumentEvent e) {
}
#Override
public void changedUpdate(DocumentEvent e) {
}
});
plus this method:
public static String getRemovedString(DocumentEvent e) {
try {
Field editsField = null;
Field[] fields = CompoundEdit.class.getDeclaredFields();
for(Field f : fields) {
if(f.getName().equals("edits")) {
editsField = f;
break;
}
}
editsField.setAccessible(true);
List edits = (List) editsField.get(e);
if(edits.size() != 1) {
return null;
}
Class<?> removeUndo = null;
for(Class<?> c : GapContent.class.getDeclaredClasses()) {
if(c.getSimpleName().equals("RemoveUndo")) {
removeUndo = c;
break;
}
}
Object removeUndoInstance = edits.get(0);
fields = removeUndo.getDeclaredFields();
Field stringField = null;
for(Field f : fields) {
if(f.getName().equals("string")) {
stringField = f;
break;
}
}
stringField.setAccessible(true);
return (String) stringField.get(removeUndoInstance);
}
catch(SecurityException e1) {
e1.printStackTrace();
}
catch(IllegalArgumentException e1) {
e1.printStackTrace();
}
catch(IllegalAccessException e1) {
e1.printStackTrace();
}
return null;
}
I had the same problem than you. And what Xeon explained help me a lot too. But after, i found a way to do that. In my case, i created a custom StyledDocument class that extends DefaultStyledDocument:
public class CustomStyledDocument extends DefaultStyledDocument
{
public CustomStyledDocument () {
super();
}
#Override
public void insertString(int offset, String string, AttributeSet as) throws BadLocationException {
super.insertString(offset, string, as);
}
#Override
public void remove(int offset, int i1) throws BadLocationException {
String previousText = getText(offset, i1);
super.remove(offset, i1);
}
}
So if you call getText method before you call super.remove(...), you will get the previous text.