Я новичок в разработке Android и работаю над написанием клиента Twitter, и я пытаюсь реализовать pull для обновления, чтобы обновить временную шкалу пользователей.
В настоящее время временная шкала обновляется с использованием обработчика, который каждый раз выбирает новые твиты. 30 секунд внутри службы, но добавление pull для обновления было бы улучшением.
Я потратил некоторое время на изучение того, как это сделать, и использование метода notifyDataSetChanged на моем адаптере, кажется, путь, ноЯ не могу заставить его работать.
Я также пытался использовать GetTweets AsyncTask из класса обслуживания, но все еще не повезло.
Я удалил некоторые определения методов и clickListeners из кода, чтобы попробовать исделать это немного легче читать здесь.
Я уверен, что упускаю что-то простое с этим, но я не знаю, с чего начать. Заранее благодарим за любую помощь!
MainAcitivity.java
public class MainActivity extends AppCompatActivity implements OnClickListener {
//Variable declarations
//Developer keys provided by Twitter through a developer account
//Developer account key for this app
public final static String TWIT_KEY = "xxxxxxxxxx";
//Developer key secret for the app
public final static String TWIT_SECRET = "xxxxxxxxxx";
//Application callback URL
public final static String TWIT_URL = "xxxxxxxxxx://";
//Twitter instance used with Twitter4j library to provide functionality
private Twitter timelineTwitter;
//Request token for accessing user account
private RequestToken requestToken;
//Shared preferences to store user details
private SharedPreferences mainActivityPrefs;
//Main view for the home timeline_layout, displays Tweets in chronological order
private ListView homeTimeline;
//SQLite database of tweets posted by users the application user follows and tweet they post
private SQLiteDatabase timelineDB;
//Cursor used for handling the SQLite database
private Cursor timelineCursor;
//Adapter class used to map data from the database and display to the user through the timeline_layout_layout.xml layout
private TimelineAdapter timelineAdapter;
//Uri to verify the application and user
Uri twitURI;
//Log tag used in error catching
private String LOG_TAG = "MainActivity";
//Broadcast receiver to receive updates when new tweets are available for the user's timeline_layout
private BroadcastReceiver statusReceiver;
//Application context set in onCreate method when activity starts
Context mainActivityContext;
private SwipeRefreshLayout timelineSwipeRefreshLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Assigning the application's context to the mainActivityContext variable
mainActivityContext = this;
//Checks the device has Biometrics enabled. Method declared in MainActivity
checkBiometricSupport();
//Get and set the shared preferences for the application
mainActivityPrefs = getSharedPreferences("sharedPrefs", 0);
//Check if user is already signed in and shared preferences are available
if (mainActivityPrefs.getString("user_token", null) == null) {
//No shared preferences stored so prompt the user to sign in. Display login_layoutxml
setContentView(R.layout.login_layout);
//Set up sign in button and listener ready for user to click
Button signIn = findViewById(R.id.login);
signIn.setOnClickListener(this);
//Configure and build an instance of Twitter for use in the MainActivity Class
ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setDebugEnabled(true)
.setOAuthConsumerKey(TWIT_KEY)
.setOAuthConsumerSecret(TWIT_SECRET);
timelineTwitter = new TwitterFactory(cb.build()).getInstance();
//Try to get a request token for session in background using AsyncTask to avoid network exception on main thread
//Class declared in MainActivity
CheckRequestToken checkRequestToken = new CheckRequestToken();
checkRequestToken.execute();
} else {
//Else if shared preferences contain user data and access tokens set up the timeline_layout and display timeline_layout.xml
//Method declared in MainActivity
setupTimeline();
}//End of if-else statement
}//End of onCreate definition
//Create options menu in top right of timeline_layout.xml containing 'Information' and 'Sign out' options
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menubar, menu);
return true;
}//End of onCreateOptionsMenu definition
@Override
public boolean onOptionsItemSelected(MenuItem item){
switch(item.getItemId()){
case R.id.information:
//Create an AlertDialog to display the application's information. Inflates information_alertdialog.xml over timeline_layout.xml
AlertDialog.Builder information = new AlertDialog.Builder(mainActivityContext,R.style.alertDialog);
LayoutInflater inflater = (LayoutInflater)mainActivityContext.getSystemService(LAYOUT_INFLATER_SERVICE);
View infoPopup = null;
if (inflater != null) {
infoPopup = inflater.inflate(R.layout.information_alertdialog, null);
}
//Sets the layouts items to values before displaying AlertDialog to the user
assert infoPopup != null;
TextView title = infoPopup.findViewById(R.id.infoHeading);
title.setText(R.string.information);
TextView purposeMessage = infoPopup.findViewById(R.id.purposeMessage);
purposeMessage.setText(R.string.applicationPurpose);
TextView infoMessage = infoPopup.findViewById(R.id.infoMessage);
infoMessage.setText(R.string.applicationInformation);
information.setNeutralButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
information.setView(infoPopup);
information.show();
//Return to activity
return true;
case R.id.signout:
//Restart activity and erase cookies for sign in
//Method declared in MainActivity
logout(mainActivityContext);
return true;
}
return super.onOptionsItemSelected(item);
}//End of onOptionsItemSelected definition
// onNewIntent fires when user returns from Twitter authentication Web page after sign in
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
//get the retrieved data from sign in with OAuth
twitURI = intent.getData();
//make sure the url is correct
if(twitURI!=null && twitURI.toString().startsWith(TWIT_URL))
{
//Method to set shared preferences using data returned after sign in using OAuth
VerifyBioAuth verifyBioAuth = new VerifyBioAuth();
verifyBioAuth.execute();
}
}//End of onNewIntent definition
//Click listener handles sign in and create_tweet button presses
public void onClick(View v) {
//find view
switch(v.getId()) {
//'Login' button pressed on login_layout.xml
case R.id.login:
//Launch a browser an take user to twitter authentication web page to allow app access to their twitter account
String authURL = requestToken.getAuthenticationURL();
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(authURL));
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.addFlags(Intent.FLAG_FROM_BACKGROUND);
startActivity(intent);
break;
//User has pressed create_tweet button in timeline_layout.xml
case R.id.tweetButton:
//launch TweetClass.class activity for user to type tweet and post it
startActivity(new Intent(this, TweetClass.class));
break;
case R.id.messageButton:
//Takes the user to their Inbox. Launches InboxActivity.class
startActivity(new Intent(this, InboxActivity.class));
break;
default:
break;
}//End of switch statement
}//End of onClick definition
//Class to implement BroadcastReceiver to receiver new updates to users timeline
class TwitterUpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//Sets the number of tweets the SQLite database can store to 100
int rowLimit = 100;
//Deletes old tweets which will be put into rows above 100 when new tweets are received
if(DatabaseUtils.queryNumEntries(timelineDB, "home")>rowLimit) {
String deleteQuery = "DELETE FROM home WHERE "+ BaseColumns._ID+" NOT IN " +
"(SELECT "+BaseColumns._ID+" FROM home ORDER BY "+"update_time DESC " +
"limit "+rowLimit+")";
timelineDB.execSQL(deleteQuery);
}
//Gets new tweets and uses TimelineAdapter class to map them from the database to the timeline_layout.xml view
timelineCursor = timelineDB.query("home", null, null, null, null, null, "update_time DESC");
startManagingCursor(timelineCursor);
timelineAdapter = new TimelineAdapter(context, timelineCursor);
homeTimeline.setAdapter(timelineAdapter);
}//End of onReceive
}//End of TwitterUpdateReceiver definition
//Stops the TimelineService class retrieving updates from Twitter and unregisters statusReceiver BroadcastReceiver
@Override
public void onDestroy() {
super.onDestroy();
try {
//stop the updater Service
stopService(new Intent(this, TimelineService.class));
//remove receiver register
unregisterReceiver(statusReceiver);
}
catch(Exception se) { Log.e(LOG_TAG, "unable to stop Service or receiver"); }
}//End of onDestroy definition
//setupTimeline displays the user's main home Twitter using the timeline_layout.xml
private void setupTimeline() {
setContentView(R.layout.timeline_layout); //Sets the timeline_layout layout style using timeline_layout.xmlout.xml
//setup onClickListener for tweetButton. Launches TweetClass.class activity for user to type tweet and post it
ImageButton tweetClicker = findViewById(R.id.tweetButton);
tweetClicker.setOnClickListener(this);
//setup onClickListener for messageButton. Launches InboxActivity.class activity for user to view their inbox of direct messages
ImageButton messageClicker = findViewById(R.id.messageButton);
messageClicker.setOnClickListener(this);
//Try and set up the users timeline and get instances of other classes used in the process
try {
//Set timelineSwipeRefreshLayout and setOnRefreshListener. Display Toast notification to user that timeline
//has been refreshed and notify timelineAdapter the data has changed
timelineSwipeRefreshLayout = findViewById(R.id.timelineSwipeLayout);
timelineSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
Toast.makeText(mainActivityContext, "Timeline refreshed", Toast.LENGTH_SHORT).show();
timelineAdapter.notifyDataSetChanged();
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
if(timelineSwipeRefreshLayout.isRefreshing()) {
timelineSwipeRefreshLayout.setRefreshing(false);
}
}
}, 1000);
}//End of onRefresh()
});//End of setOnRefreshListener
//get reference to the list view item of timeline_layout which shows tweet.xml layout for individual tweets
homeTimeline = findViewById(R.id.timelineList);
//Database helper for tweets. Gets tweet data ready to be displayed
TimelineDataHelper timelineHelper = new TimelineDataHelper(this);
//Gets the database of stored tweets
timelineDB = timelineHelper.getReadableDatabase();
//Query the database sorting the tweets in order of newest to oldest
timelineCursor = timelineDB.query
("home", null, null, null, null, null, "update_time DESC");
//Use a cursor to manage the tweet updates
startManagingCursor(timelineCursor);
//Get an instance of the TimelineAdapter class to format data from database to display to the user
timelineAdapter = new TimelineAdapter(this, timelineCursor);
//Populates the timeline_layout.xml ListView item with retrieved Tweets. Each list item uses the tweet.xml layout
homeTimeline.setAdapter(timelineAdapter);
//Get an instance of the receiver to send notification when new tweets are available
statusReceiver = new TwitterUpdateReceiver();
//Register a receiver to receive the notifications of newly available tweets
registerReceiver(statusReceiver, new IntentFilter("TWITTER_UPDATES"));
//Start the service which retrieves the tweets from the users Twitter account
this.getApplicationContext().startService(new Intent(this.getApplicationContext(), TimelineService.class));
}
catch(Exception te) { Log.e(LOG_TAG, "Failed to fetch timeline_layout: " + te.getMessage()); }
}//End of setupTimeline method
TimelineAdapter.java
public class TimelineAdapter extends SimpleCursorAdapter {
//twitter developer key
private final static String TWIT_KEY = "xxxxxxxxxxs";
//twitter developer secret
private final static String TWIT_SECRET = "xxxxxxxxxx";
//strings representing database column names to map to views
private static final String[] from = { "update_text", "user_screen",
"update_time", "user_img" };
//view item IDs for mapping database record values to
private static final int[] to = { R.id.updateText, R.id.userScreen,
R.id.updateTime, R.id.userImg };
//Context passed by constructor
private Context adapterContext;
//Instance of Twitter for use in the adapter
private Twitter adapterTwitter;
private String applicationUser;
private Context mContext;
//constructor sets up adapter, passing 'from' data and 'to' views
TimelineAdapter(Context context, Cursor c) {
super(context, R.layout.tweet, c, from, to);
adapterContext = context;
//Shared preferences for application
SharedPreferences sharedPrefs = context.getSharedPreferences("sharedPrefs", 0);
String userToken = sharedPrefs.getString("user_token", null);
String userSecret = sharedPrefs.getString("user_secret", null);
//create new Twitter configuration for use by adapter
Configuration twitConf = new ConfigurationBuilder()
.setOAuthConsumerKey(TWIT_KEY)
.setOAuthConsumerSecret(TWIT_SECRET)
.setOAuthAccessToken(userToken)
.setOAuthAccessTokenSecret(userSecret)
.setTweetModeExtended(true)
.build();
//create Twitter instance for retweeting
adapterTwitter = new TwitterFactory(twitConf).getInstance();
applicationUser = sharedPrefs.getString("twitter_user",null);
mContext = context;
}//End of constructor
//Bind the data to the visible views
@Override
public void bindView(View row, Context context, Cursor cursor) {
super.bindView(row, context, cursor);
//Try and get profileImage from URL stored in SQLite database
try {
URL profileURL =
new URL(cursor.getString(cursor.getColumnIndex("user_img")));
ImageView profPic = row.findViewById(R.id.userImg);
Picasso.get().load(profileURL.toString()).into(profPic);
}catch(Exception te) { //Tag for logged events
String LOG_TAG = "TimelineAdapter";
Log.e(LOG_TAG, "" + te.getMessage()); }
//get the tweet time
long createdAt = cursor.getLong(cursor.getColumnIndex("update_time"));
//get the tweet time view
TextView textCreatedAt = row.findViewById(R.id.updateTime);
//adjust the way the time is displayed to make it human-readable
textCreatedAt.setText(DateUtils.getRelativeTimeSpanString(createdAt));
//get the status ID
long statusID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID));
//get the user name
String statusName = cursor.getString(cursor.getColumnIndex("user_screen"));
//create a UserData object to store these
UserData tweetData = new UserData(statusID, statusName);
//set the status data object as tag for both retweet and reply and flag tweet buttons
row.findViewById(R.id.retweet).setTag(tweetData);
row.findViewById(R.id.reply).setTag(tweetData);
row.findViewById(R.id.tweetMenu).setTag(tweetData);
//setup onclick listeners for buttons in tweet.xml
row.findViewById(R.id.retweet).setOnClickListener(tweetListener);
row.findViewById(R.id.reply).setOnClickListener(tweetListener);
row.findViewById(R.id.tweetMenu).setOnClickListener(tweetListener);
//setup onclick for the user screen name within the tweet.xml
row.findViewById(R.id.userScreen).setOnClickListener(tweetListener);
}//End of bindView
}//End of class definition
TimelineService.java
public class TimelineService extends Service {
//twitter authentication key
public final static String TWIT_KEY = "xxxxxxxxxx";
//twitter secret
public final static String TWIT_SECRET = "xxxxxxxxxx";
SharedPreferences timelineServicePrefs;
//twitter object
private Twitter timelineTwitter;
//timeline_layout database
private SQLiteDatabase timelineDB;
//handler for updater
public Handler timelineHandler;
//updater thread object
private TimelineUpdater timelineUpdater;
private boolean statusChanges;
@Override
public void onCreate(){
super.onCreate();
//Setting up the class
//get preferences
//shared preferences for user details
timelineServicePrefs = getSharedPreferences("sharedPrefs", 0);
//get user preferences
String userToken = timelineServicePrefs.getString("user_token", null);
String userSecret = timelineServicePrefs.getString("user_secret", null);
//database helper object
TimelineDataHelper timelineHelper = new TimelineDataHelper(this);
//get the database
timelineDB = timelineHelper.getWritableDatabase();
//create new configuration
Configuration twitConf = new ConfigurationBuilder()
.setOAuthConsumerKey(TWIT_KEY)
.setOAuthConsumerSecret(TWIT_SECRET)
.setOAuthAccessToken(userToken)
.setOAuthAccessTokenSecret(userSecret)
.build();
//instantiate new twitter
timelineTwitter = new TwitterFactory(twitConf).getInstance();
}//End of onCreate
@Override
public IBinder onBind(Intent intent) {
return null;
}//End of onBind
// TimelineUpdater class implements the runnable interface
class TimelineUpdater implements Runnable{
//run method
public void run() {
//check for updates - assume none
statusChanges = false;
//get tweets in AsyncTask to avoid network exception on main thread
GetTweetsTask getTweetsTask = new GetTweetsTask();
getTweetsTask.execute();
//delay fetching new updates
timelineHandler.postDelayed(this, 30 * 1000);//Fetch new tweets every 30 seconds
}
}//End of TimelineUpdater
class GetTweetsTask extends AsyncTask<Void, Void, List<Status>> {
@Override
protected List<twitter4j.Status> doInBackground(Void... voids) {
try {
//fetch timeline_layout
//retrieve the new home timeline_layout tweets as a list
List<twitter4j.Status> homeTimeline = timelineTwitter.getHomeTimeline();
//iterate through new status updates
for (twitter4j.Status statusUpdate : homeTimeline)
{
//call the getValues method of the data helper class, passing the new updates
ContentValues timelineValues = TimelineDataHelper.getValues(statusUpdate);
//if the database already contains the updates they will not be inserted
timelineDB.insertWithOnConflict("home", null, timelineValues,SQLiteDatabase.CONFLICT_REPLACE);
//confirm we have new updates
statusChanges = true;
if (statusChanges)
{
//this should be received in the main timeline_layout class
sendBroadcast(new Intent("TWITTER_UPDATES"));
}
}
}
catch (Exception te) { //debugging tag
String LOG_TAG = "TimelineService";
Log.e(LOG_TAG, "Exception: " + te); }
return null;
}
@Override
protected void onPostExecute(List<twitter4j.Status> homeTimeline) {
super.onPostExecute(homeTimeline);
}
}//End of GetTweetsTask
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
//get handler
timelineHandler = new Handler();
//create an instance of the updater class
timelineUpdater = new TimelineUpdater();
//add to run queue
timelineHandler.post(timelineUpdater);
//return sticky
return START_STICKY;
}//End of onStartCommand
@Override
public void onDestroy() {
super.onDestroy();
//stop the updating
this.timelineHandler.removeCallbacks(timelineUpdater);
}//End of onDestroy
}//End of class definition